eog/libeog/eog-wrap-list.c
Jens Finke 2cc77bd87b This is a fairly large patch, which makes fundamental changes. It is a
2004-05-25  Jens Finke  <jens@triq.net>

	This is a fairly large patch, which makes fundamental changes. It
	is a work in progress and is not fully functional yet again. Use
	the BEFORE_GTK_UI_MANAGER_SWITCH tag to checkout the latest
	working version.

	* libeog/Makefile.am: Moved eog-wrap-list.[ch],
	eog-canvas-pixbuf.[ch], eog-collection-item.[ch] here.

	* collection/Makefile.am: Removed above files from here. Killed
	eog-collection-marshal.list.

	* shell/Makefile.am: Link with jpegutils.

	* shell/eog-window.c: Made this a GtkWindow, using GtkUIManager
	and related stuff.

	* shell/gtk-shell-ui.xml: New file.
2004-05-25 05:55:24 +00:00

1328 lines
32 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Eog Of Gnome - view of the image collection
*
* Copyright (C) 2001-2003 The Free Software Foundation
*
* Author: Jens Finke <jens@gnome.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <math.h>
#include <string.h>
#include <glib/gmain.h>
#include <glib/gmacros.h>
#include <libgnome/gnome-macros.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkdnd.h>
#include "eog-wrap-list.h"
#include "libeog-marshal.h"
#include "eog-image.h"
#include "eog-collection-item.h"
#define COLLECTION_DEBUG 0
#define EOG_WRAP_LIST_BORDER 12
static const int MAX_ITEM_WIDTH = EOG_COLLECTION_ITEM_MAX_WIDTH;
#define EOG_DND_URI_LIST_TYPE "text/uri-list"
enum {
EOG_DND_URI_LIST
};
static GtkTargetEntry drag_types [] = {
{ EOG_DND_URI_LIST_TYPE, 0, EOG_DND_URI_LIST }
};
/* Used to hold signal handler IDs for models */
typedef enum {
MODEL_SIGNAL_PREPARED,
MODEL_SIGNAL_IMAGE_ADDED,
MODEL_SIGNAL_IMAGE_REMOVED,
MODEL_SIGNAL_LAST
} ModelSignal;
enum {
SELECTION_CHANGED,
RIGHT_CLICK,
DOUBLE_CLICK,
LAST_SIGNAL
};
static guint eog_wrap_list_signals [LAST_SIGNAL];
enum {
GLOBAL_WIDGET_SIZE_CHANGED,
GLOBAL_SPACING_CHANGED,
ITEM_SIZE_CHANGED,
UPDATE_HINT_LAST
};
typedef struct {
ModelSignal hint;
GQuark id;
} ItemUpdate;
struct _EogWrapListPrivate {
/* List of all items currently in the view. */
GHashTable *item_table;
/* Sorted list of items */
GList *view_order;
/* list of all selected items */
GList *selected_items;
/* number of selected items to prevent a slow
* g_list_length */
int n_selected_items;
/* Model to use. */
EogImageList *model;
/* Signal connection IDs for the models */
int model_ids[MODEL_SIGNAL_LAST];
/* Group which hold all the items. */
GnomeCanvasItem *item_group;
/* spacing between items */
guint row_spacing;
guint col_spacing;
/* size of items */
guint item_width;
guint item_height;
/* Number of items */
guint n_items;
/* Number of rows. */
guint n_rows;
/* Number of columns. */
guint n_cols;
/* Id for the idle handler.*/
gint idle_handler_id;
/* Update hints */
gboolean global_update_hints [UPDATE_HINT_LAST];
/* last id of thumbnail the user clicked */
GnomeCanvasItem *last_item_clicked;
};
static void eog_wrap_list_class_init (EogWrapListClass *class);
static void eog_wrap_list_instance_init (EogWrapList *wlist);
static void eog_wrap_list_dispose (GObject *object);
static void eog_wrap_list_finalize (GObject *object);
static void eog_wrap_list_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
static gboolean eog_wrap_list_key_press_cb (GtkWidget *widget, GdkEventKey *event);
static void eog_wrap_list_drag_data_get_cb (GtkWidget *widget,
GdkDragContext *drag_context,
GtkSelectionData *data,
guint info,
guint time);
static void request_update (EogWrapList *wlist);
static gboolean do_update (EogWrapList *wlist);
static gint handle_canvas_click (GnomeCanvas *canvas, GdkEventButton *event, gpointer data);
GNOME_CLASS_BOILERPLATE (EogWrapList,
eog_wrap_list,
GnomeCanvas,
GNOME_TYPE_CANVAS);
/* Class initialization function for the abstract wrapped list view */
static void
eog_wrap_list_class_init (EogWrapListClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
object_class->dispose = eog_wrap_list_dispose;
object_class->finalize = eog_wrap_list_finalize;
widget_class->size_allocate = eog_wrap_list_size_allocate;
widget_class->key_press_event = eog_wrap_list_key_press_cb;
widget_class->drag_data_get = eog_wrap_list_drag_data_get_cb;
eog_wrap_list_signals [SELECTION_CHANGED] =
g_signal_new ("selection_changed",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EogWrapListClass, selection_changed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
eog_wrap_list_signals [RIGHT_CLICK] =
g_signal_new ("right_click",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EogWrapListClass, right_click),
NULL,
NULL,
libeog_marshal_BOOLEAN__INT_POINTER,
G_TYPE_BOOLEAN,
2,
G_TYPE_INT,
G_TYPE_POINTER);
eog_wrap_list_signals [DOUBLE_CLICK] =
g_signal_new ("double_click",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EogWrapListClass, double_click),
NULL,
NULL,
libeog_marshal_VOID__INT,
G_TYPE_NONE,
1,
G_TYPE_INT);
}
/* object initialization function for the abstract wrapped list view */
static void
eog_wrap_list_instance_init (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
priv = g_new0 (EogWrapListPrivate, 1);
wlist->priv = priv;
priv->item_table = NULL;
priv->view_order = NULL;
priv->selected_items = NULL;
priv->n_selected_items = 0;
priv->model = NULL;
priv->row_spacing = 0;
priv->col_spacing = 0;
priv->idle_handler_id = -1;
priv->n_items = 0;
priv->n_rows = 0;
priv->n_cols = 0;
priv->last_item_clicked = NULL;
priv->item_width = -1;
priv->item_height = -1;
/* Drag source */
gtk_drag_source_set (GTK_WIDGET (wlist),
GDK_BUTTON1_MASK,
drag_types, G_N_ELEMENTS (drag_types),
GDK_ACTION_COPY);
}
/* Destroy handler for the abstract wrapped list view */
static void
eog_wrap_list_dispose (GObject *object)
{
EogWrapList *wlist;
EogWrapListPrivate *priv;
g_return_if_fail (object != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (object));
wlist = EOG_WRAP_LIST (object);
priv = wlist->priv;
if (priv->model)
g_object_unref (G_OBJECT (priv->model));
priv->model = NULL;
/* FIXME: free the items and item array */
GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
eog_wrap_list_finalize (GObject *object)
{
EogWrapList *wlist;
g_return_if_fail (object != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (object));
wlist = EOG_WRAP_LIST (object);
if (wlist->priv)
g_free (wlist->priv);
wlist->priv = NULL;
if (G_OBJECT_CLASS (parent_class)->finalize)
(* G_OBJECT_CLASS (parent_class)->finalize) (object);
}
static void
eog_wrap_list_construct (EogWrapList *wlist)
{
wlist->priv->item_table = g_hash_table_new ((GHashFunc) g_direct_hash,
(GCompareFunc) g_direct_equal);
g_signal_connect_after (G_OBJECT (wlist),
"button-press-event",
G_CALLBACK (handle_canvas_click),
wlist);
}
GtkWidget*
eog_wrap_list_new (void)
{
GtkWidget *wlist;
wlist = gtk_widget_new (eog_wrap_list_get_type (),
"can-focus", TRUE,
"events",
GDK_EXPOSURE_MASK |
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_KEY_PRESS_MASK,
NULL);
eog_wrap_list_construct (EOG_WRAP_LIST (wlist));
return wlist;
}
static gint
get_item_view_position (EogWrapList *wlist, GnomeCanvasItem *item)
{
EogWrapListPrivate *priv;
guint row, col, n;
priv = wlist->priv;
col = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "column"));
row = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "row"));
n = col + priv->n_cols * row;
return n;
}
static void
ensure_item_is_visible (EogWrapList *wlist, GnomeCanvasItem *item)
{
EogWrapListPrivate *priv;
double x1, y1, x2, y2;
int width, height;
int ofsx, ofsy;
int scroll_x, scroll_y;
priv = wlist->priv;
width = GTK_WIDGET (wlist)->allocation.width;
height = GTK_WIDGET (wlist)->allocation.height;
gnome_canvas_get_scroll_offsets (GNOME_CANVAS (wlist), &ofsx, &ofsy);
gnome_canvas_item_get_bounds (item, &x1, &y1, &x2, &y2);
scroll_x = scroll_y = 0;
if (y1 < ofsy || y2 > (ofsy + height)) {
if (abs (y1 - ofsy) < abs (y1 - (ofsy + height))) {
/* the item is more at the upper edge */
scroll_y = y1 - ofsy;
}
else {
/* the item is more at the lower edge */
scroll_y = y2 - (ofsy + height);
}
}
if (x1 < ofsx || x2 > (ofsx + width)) {
if (abs (x1 - ofsx) < abs (x1 - (ofsx + width))) {
/* the item is more at the left edge */
scroll_x = x1 - ofsx;
}
else {
/* the item is more at the right edge */
scroll_x = x2 - (ofsx + width);
}
}
gnome_canvas_scroll_to (GNOME_CANVAS (wlist),
ofsx + scroll_x, ofsy + scroll_y);
}
static gboolean
set_select_status (EogWrapList *wlist, EogCollectionItem *item, gboolean state)
{
EogWrapListPrivate *priv;
priv = wlist->priv;
if (state && !eog_collection_item_is_selected (item)) {
eog_collection_item_set_selected (item, state);
priv->selected_items = g_list_append (priv->selected_items, g_object_ref (item));
priv->n_selected_items++;
return TRUE;
}
else if (!state && eog_collection_item_is_selected (item)) {
eog_collection_item_set_selected (item, state);
priv->selected_items = g_list_remove (priv->selected_items, item);
g_object_unref (item);
priv->n_selected_items--;
g_assert (priv->n_selected_items >= 0);
return TRUE;
}
return FALSE;
}
static void
toggle_select_status (EogWrapList *wlist, EogCollectionItem *item)
{
set_select_status (wlist, item, !eog_collection_item_is_selected (item));
}
static void
deselect_all (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *it;
priv = wlist->priv;
for (it = priv->selected_items; it != NULL; it = it->next) {
EogCollectionItem *item = EOG_COLLECTION_ITEM (it->data);
eog_collection_item_set_selected (item, FALSE);
g_object_unref (item);
}
priv->n_selected_items = 0;
if (priv->selected_items != NULL) {
g_list_free (priv->selected_items);
priv->selected_items = NULL;
}
}
static gint
handle_canvas_click (GnomeCanvas *canvas, GdkEventButton *event, gpointer data)
{
EogWrapList *wlist;
EogWrapListPrivate *priv;
g_return_val_if_fail (data != NULL, FALSE);
g_return_val_if_fail (EOG_IS_WRAP_LIST (data), FALSE);
wlist = EOG_WRAP_LIST (data);
priv = wlist->priv;
if (!GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (wlist)))
gtk_widget_grab_focus (GTK_WIDGET (wlist));
if (priv->n_selected_items > 1) {
EogCollectionItem *item;
item = EOG_COLLECTION_ITEM (priv->selected_items->data);
deselect_all (wlist);
set_select_status (wlist, item, TRUE);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
wlist->priv->last_item_clicked = GNOME_CANVAS_ITEM (item);
}
return TRUE;
}
static gint
handle_item_event (GnomeCanvasItem *item, GdkEvent *event, EogWrapList *wlist)
{
EogWrapListPrivate *priv;
EogImageList *model;
EogCollectionItem *eci;
GQuark id = 0;
gboolean ret_val = TRUE;
gboolean selection_changed = FALSE;
g_return_val_if_fail (EOG_IS_WRAP_LIST (wlist), FALSE);
if (!GTK_WIDGET_HAS_FOCUS (GTK_WIDGET (wlist)))
gtk_widget_grab_focus (GTK_WIDGET (wlist));
priv = wlist->priv;
model = priv->model;
if (model == NULL) return FALSE;
eci = EOG_COLLECTION_ITEM (item);
switch (event->type) {
case GDK_BUTTON_PRESS:
switch (event->button.button) {
case 3:
/* Right click */
selection_changed = set_select_status (wlist, eci, TRUE);
g_signal_emit (GTK_OBJECT (wlist),
eog_wrap_list_signals [RIGHT_CLICK], 0,
id, event, &ret_val);
g_signal_stop_emission_by_name (G_OBJECT (item->canvas),
"button-press-event");
break;
case 2:
/* Middle button */
g_warning ("Implement D&D!");
break;
case 1:
/* Left click */
if (event->button.state & GDK_SHIFT_MASK) {
/* shift key pressed */
if (wlist->priv->last_item_clicked == NULL) {
toggle_select_status (wlist, eci);
selection_changed = TRUE;
}
else {
int pos;
int prev_pos;
GList *start_node;
GList *end_node;
GList *node;
gboolean changed;
pos = get_item_view_position (wlist, item);
prev_pos = get_item_view_position (wlist, priv->last_item_clicked);
if (pos > prev_pos) {
start_node = g_list_find (priv->view_order, priv->last_item_clicked);
end_node = g_list_find (priv->view_order, item);
}
else {
start_node = g_list_find (priv->view_order, item);
end_node = g_list_find (priv->view_order, priv->last_item_clicked);
}
for (node = start_node; node != end_node->next; node = node->next) {
changed = set_select_status (wlist, EOG_COLLECTION_ITEM (node->data), TRUE);
selection_changed |= changed;
}
}
} else if (event->button.state & GDK_CONTROL_MASK) {
/* control key pressed */
/* add item with id to selection*/
toggle_select_status (wlist, eci);
selection_changed = TRUE;
} else {
/* clear selection */
deselect_all (wlist);
/* select only item with id */
toggle_select_status (wlist, eci);
selection_changed = TRUE;
}
wlist->priv->last_item_clicked = item;
ensure_item_is_visible (wlist, item);
/*
* stop further event handling through
* handle_canvas_click
*/
g_signal_stop_emission_by_name (G_OBJECT (item->canvas),
"button-press-event");
}
break;
case GDK_2BUTTON_PRESS:
/* stop further event handling through handle_canvas_click */
g_signal_stop_emission_by_name (G_OBJECT (item->canvas), "button-press-event");
g_signal_emit (G_OBJECT (wlist),
eog_wrap_list_signals [DOUBLE_CLICK], 0,
id);
break;
default:
ret_val = FALSE;
}
if (selection_changed) {
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
return ret_val;
}
static void
handle_item_size_changed (GnomeCanvasItem *item, EogWrapList *wlist)
{
EogWrapListPrivate *priv;
priv = wlist->priv;
priv->global_update_hints [ITEM_SIZE_CHANGED] = TRUE;
request_update (wlist);
}
/* Size_allocate handler for the abstract wrapped list view */
static void
eog_wrap_list_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
EogWrapList *wlist;
EogWrapListPrivate *priv;
wlist = EOG_WRAP_LIST (widget);
priv = wlist->priv;
GNOME_CALL_PARENT (GTK_WIDGET_CLASS, size_allocate, (widget, allocation));
priv->global_update_hints[GLOBAL_WIDGET_SIZE_CHANGED] = TRUE;
request_update (wlist);
}
static void
eog_wrap_list_select_up (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *node;
GnomeCanvasItem *item;
int i;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
if (priv->n_selected_items != 1) return;
node = g_list_find (priv->view_order, priv->last_item_clicked);
for (i = 0; i < priv->n_cols && node != NULL; i++) {
node = node->prev;
}
if (node == NULL) return;
item = GNOME_CANVAS_ITEM (node->data);
set_select_status (wlist, EOG_COLLECTION_ITEM (priv->last_item_clicked), FALSE);
set_select_status (wlist, EOG_COLLECTION_ITEM (item), TRUE);
priv->last_item_clicked = item;
ensure_item_is_visible (wlist, item);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
static void
eog_wrap_list_select_down (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *node;
GnomeCanvasItem *item;
int i;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
if (priv->n_selected_items != 1) return;
node = g_list_find (priv->view_order, priv->last_item_clicked);
for (i = 0; i < priv->n_cols && node != NULL; i++) {
node = node->next;
}
if (node == NULL) return;
item = GNOME_CANVAS_ITEM (node->data);
set_select_status (wlist, EOG_COLLECTION_ITEM (priv->last_item_clicked), FALSE);
set_select_status (wlist, EOG_COLLECTION_ITEM (item), TRUE);
priv->last_item_clicked = item;
ensure_item_is_visible (wlist, item);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
void
eog_wrap_list_select_left (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *node;
GnomeCanvasItem *item;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
if (priv->n_selected_items != 1) return;
node = g_list_find (priv->view_order, priv->last_item_clicked);
if (node == NULL) return;
node = node->prev;
if (node == NULL) {
node = g_list_last (priv->view_order);
}
item = GNOME_CANVAS_ITEM (node->data);
set_select_status (wlist, EOG_COLLECTION_ITEM (priv->last_item_clicked), FALSE);
set_select_status (wlist, EOG_COLLECTION_ITEM (item), TRUE);
priv->last_item_clicked = item;
ensure_item_is_visible (wlist, item);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
void
eog_wrap_list_select_right (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *node;
GnomeCanvasItem *item;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
if (priv->n_selected_items != 1) return;
node = g_list_find (priv->view_order, priv->last_item_clicked);
if (node == NULL) return;
node = node->next;
if (node == NULL) {
node = g_list_first (priv->view_order);
}
item = GNOME_CANVAS_ITEM (node->data);
set_select_status (wlist, EOG_COLLECTION_ITEM (priv->last_item_clicked), FALSE);
set_select_status (wlist, EOG_COLLECTION_ITEM (item), TRUE);
priv->last_item_clicked = item;
ensure_item_is_visible (wlist, item);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
static gboolean
eog_wrap_list_key_press_cb (GtkWidget *widget, GdkEventKey *event)
{
gboolean handled = FALSE;
switch (event->keyval) {
case GDK_Up:
eog_wrap_list_select_up (EOG_WRAP_LIST (widget));
handled = TRUE;
break;
case GDK_Down:
eog_wrap_list_select_down (EOG_WRAP_LIST (widget));
handled = TRUE;
break;
case GDK_Left:
eog_wrap_list_select_left (EOG_WRAP_LIST (widget));
handled = TRUE;
break;
case GDK_Right:
eog_wrap_list_select_right (EOG_WRAP_LIST (widget));
handled = TRUE;
break;
};
if (!handled)
handled = GNOME_CALL_PARENT_WITH_DEFAULT (GTK_WIDGET_CLASS, key_press_event, (widget, event), FALSE);
return handled;
}
static void eog_wrap_list_drag_data_get_cb (GtkWidget *widget,
GdkDragContext *drag_context,
GtkSelectionData *data,
guint info,
guint time)
{
EogWrapList *wlist;
wlist = EOG_WRAP_LIST (widget);
switch (info) {
case EOG_DND_URI_LIST:
{
EogImage *image;
GList *it;
GString *str;
GnomeVFSURI *uri;
/* We assess that every uri has about 30 chars. This
is only to reduce number of reallocations when we
add chars to the string */
str = g_string_sized_new (g_list_length (wlist->priv->selected_items) * 30);
for (it = wlist->priv->selected_items; it != NULL; it = it->next) {
EogCollectionItem *item = EOG_COLLECTION_ITEM (it->data);
image = eog_collection_item_get_image (item);
uri = eog_image_get_uri (image);
g_string_append (str, gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE));
g_string_append_c (str, '\n');
gnome_vfs_uri_unref (uri);
}
gtk_selection_data_set (data,
data->target,
8, (guchar*) str->str,
str->len);
g_string_free (str, TRUE);
break;
}
default:
g_assert_not_reached ();
}
}
static void
add_image (EogWrapList *wlist, EogImage *image)
{
EogWrapListPrivate *priv;
GnomeCanvasItem *item;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
item = eog_collection_item_new (gnome_canvas_root (GNOME_CANVAS (wlist)), image);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (handle_item_event), wlist);
g_signal_connect (G_OBJECT (item), "size_changed", G_CALLBACK (handle_item_size_changed), wlist);
priv->view_order = g_list_prepend (priv->view_order, item);
}
static void
model_prepared (EogImageList *model, gpointer data)
{
EogWrapList *wlist;
EogWrapListPrivate *priv;
EogIter *iter;
gboolean success;
EogImage *image;
GList *it;
g_return_if_fail (EOG_IS_WRAP_LIST (data));
wlist = EOG_WRAP_LIST (data);
priv = wlist->priv;
g_assert (priv->view_order == NULL);
g_assert (priv->selected_items == NULL);
g_assert (priv->n_selected_items == 0);
iter = eog_image_list_get_first_iter (model);
success = (iter != NULL);
for (; success; success = eog_image_list_iter_next (model, iter, FALSE)) {
image = eog_image_list_get_img_by_iter (model, iter);
add_image (wlist, image);
g_object_unref (image);
}
g_free (iter);
priv->view_order = g_list_reverse (priv->view_order);
priv->n_items = g_list_length (priv->view_order);
priv->global_update_hints[GLOBAL_WIDGET_SIZE_CHANGED] = TRUE;
request_update (wlist);
for (it = priv->view_order; it != NULL; it = it->next) {
eog_collection_item_load (EOG_COLLECTION_ITEM (it->data));
}
if (priv->view_order != NULL) {
deselect_all (wlist);
if (set_select_status (wlist, EOG_COLLECTION_ITEM (priv->view_order->data), TRUE)) {
priv->last_item_clicked = GNOME_CANVAS_ITEM (priv->view_order->data);
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
}
}
/* Handler for the interval_added signal from models */
static void
model_image_added (EogImageList *model, EogImage *image, int position, gpointer data)
{
GnomeCanvasItem *item;
EogWrapList *wlist;
EogWrapListPrivate *priv;
g_return_if_fail (EOG_IS_WRAP_LIST (data));
wlist = EOG_WRAP_LIST (data);
priv = wlist->priv;
/* insert image */
item = eog_collection_item_new (gnome_canvas_root (GNOME_CANVAS (wlist)), image);
g_signal_connect (G_OBJECT (item), "event", G_CALLBACK (handle_item_event), wlist);
priv->view_order = g_list_insert (priv->view_order, item, position);
priv->n_items = g_list_length (priv->view_order);
priv->global_update_hints[GLOBAL_WIDGET_SIZE_CHANGED] = TRUE;
request_update (wlist);
eog_collection_item_load (EOG_COLLECTION_ITEM (item));
}
/* Handler for the interval_changed signal from models */
static void
model_image_removed (EogImageList *model, GQuark id, gpointer data)
{
#if 0
g_return_if_fail (EOG_IS_WRAP_LIST (data));
g_message ("model_interval_removed called\n");
/* FIXME: implement this. */
#endif
}
/* Set model handler for the wrapped list view */
void
eog_wrap_list_set_model (EogWrapList *wlist, EogImageList *model)
{
EogWrapListPrivate *priv;
int i;
GList *it;
g_return_if_fail (wlist != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
if (priv->model) {
for (i = 0; i < MODEL_SIGNAL_LAST; i++) {
g_signal_handler_disconnect (G_OBJECT (priv->model), priv->model_ids[i]);
priv->model_ids[i] = 0;
}
g_object_unref (G_OBJECT (priv->model));
}
priv->model = NULL;
/* free/remove all the collection items */
for (it = priv->view_order; it != NULL; it = it->next) {
gtk_object_destroy (GTK_OBJECT (it->data));
}
g_list_free (priv->selected_items);
priv->selected_items = NULL;
priv->n_selected_items = 0;
g_list_free (priv->view_order);
priv->view_order = NULL;
if (model) {
priv->model = model;
g_object_ref (G_OBJECT (model));
priv->model_ids[MODEL_SIGNAL_PREPARED] = g_signal_connect (
G_OBJECT (model), "list-prepared",
G_CALLBACK (model_prepared),
wlist);
priv->model_ids[MODEL_SIGNAL_IMAGE_ADDED] = g_signal_connect (
G_OBJECT (model), "image-added",
G_CALLBACK (model_image_added),
wlist);
priv->model_ids[MODEL_SIGNAL_IMAGE_REMOVED] = g_signal_connect (
G_OBJECT (model), "image-removed",
G_CALLBACK (model_image_removed),
wlist);
}
request_update (wlist);
}
/**
* eog_wrap_list_set_row_spacing:
* @wlist: A wrapped list view.
* @spacing: Spacing between rows in pixels.
*
* Sets the spacing between the rows of a wrapped list view.
**/
void
eog_wrap_list_set_row_spacing (EogWrapList *wlist, guint spacing)
{
EogWrapListPrivate *priv;
g_return_if_fail (wlist != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
priv->row_spacing = spacing;
priv->global_update_hints[GLOBAL_SPACING_CHANGED] = TRUE;
request_update (wlist);
}
/**
* eog_wrap_list_set_col_spacing:
* @wlist: A wrapped list view.
* @spacing: Spacing between columns in pixels.
*
* Sets the spacing between the columns of a wrapped list view.
**/
void
eog_wrap_list_set_col_spacing (EogWrapList *wlist, guint spacing)
{
EogWrapListPrivate *priv;
g_return_if_fail (wlist != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
priv = wlist->priv;
priv->col_spacing = spacing;
priv->global_update_hints[GLOBAL_SPACING_CHANGED] = TRUE;
request_update (wlist);
}
static void
request_update (EogWrapList *wlist)
{
g_return_if_fail (wlist != NULL);
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
#if COLLECTION_DEBUG
g_message ("request_update called.");
#endif
if (wlist->priv->idle_handler_id == -1)
{
wlist->priv->idle_handler_id = g_idle_add ((GSourceFunc) do_update, wlist);
}
}
static gboolean
do_layout_check (EogWrapList *wlist)
{
unsigned int n_rows_new = 0;
unsigned int n_cols_new = 0;
gint cw;
gint ch;
EogWrapListPrivate *priv;
priv = wlist->priv;
#if COLLECTION_DEBUG
g_message ("do_layout_check called");
#endif
if (priv->view_order == NULL)
return FALSE;
/* get canvas width */
cw = GTK_WIDGET (wlist)->allocation.width;
ch = GTK_WIDGET (wlist)->allocation.height;
/* calculate new number of columns/rows */
n_cols_new = cw / (EOG_COLLECTION_ITEM_MAX_WIDTH + priv->col_spacing);
if (cw > (n_cols_new * (EOG_COLLECTION_ITEM_MAX_WIDTH + priv->col_spacing) + EOG_COLLECTION_ITEM_MAX_WIDTH))
n_cols_new++;
if (n_cols_new == 0)
n_cols_new = 1;
n_rows_new = (priv->n_items + n_cols_new - 1) / n_cols_new;
#if COLLECTION_DEBUG
g_print (" ** canvas width: %i\n",cw);
g_print (" ** n_cols_new: %i\n", n_cols_new);
g_print (" ** n_rows_new: %i\n", n_rows_new);
#endif
if (n_cols_new == priv->n_cols && n_rows_new == priv->n_rows)
return FALSE;
priv->n_cols = n_cols_new;
priv->n_rows = n_rows_new;
return TRUE;
}
static void
set_item_position (EogCollectionItem *item, double x, double y)
{
double x1, x2, y1, y2;
double xoff, yoff;
gnome_canvas_item_get_bounds (GNOME_CANVAS_ITEM (item), &x1, &y1, &x2, &y2);
xoff = x - x1;
yoff = y - y1;
if (xoff || yoff)
gnome_canvas_item_move (GNOME_CANVAS_ITEM (item), xoff, yoff);
}
static void
calculate_row_height (GList *it, int n_cols,
int *image_height, int *caption_height)
{
int i;
EogCollectionItem *item;
int ih, ch, w;
*image_height = 0;
*caption_height = 0;
for (i = 0; (it != NULL) && (i < n_cols); i++, it = it->next)
{
item = EOG_COLLECTION_ITEM (it->data);
eog_collection_item_get_size (item, &w, &ih, &ch);
*image_height = MAX (*image_height, ih);
*caption_height = MAX (*caption_height, ch);
}
}
static void
do_item_rearrangement (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
int max_image_height;
int max_caption_height;
int item_width;
int image_height;
int caption_height;
int row_offset;
int n, row, col;
double world_x, world_y;
double sr_width, sr_height;
EogCollectionItem *item;
GList *it;
#if COLLECTION_DEBUG
g_message ("do_item_rearrangement called");
#endif
priv = wlist->priv;
row_offset = EOG_WRAP_LIST_BORDER - priv->row_spacing;
max_image_height = max_caption_height = 0;
it = priv->view_order;
col = 0;
for (n = 0; it != NULL; it = it->next, n++) {
col = n % priv->n_cols;
row = n / priv->n_cols;
if (col == 0) {
/* the offset for the new row is the
* old offset plus the height of the
* previous row
*/
row_offset = row_offset + max_image_height + max_caption_height +
priv->row_spacing;
/* we start a new row and calculate
* the row height/offset for the current row
*/
calculate_row_height (it, priv->n_cols,
&max_image_height, &max_caption_height);
}
item = EOG_COLLECTION_ITEM (it->data);
eog_collection_item_get_size (item, &item_width, &image_height, &caption_height);
item_width = MIN (item_width, MAX_ITEM_WIDTH); /* this is only to ensure
item_width <= MAX_ITEM_WIDTH */
world_x = col * (MAX_ITEM_WIDTH + priv->col_spacing) +
(MAX_ITEM_WIDTH - item_width) / 2;
world_y = row_offset + max_image_height - image_height;
set_item_position (item, world_x, world_y);
g_object_set_data (G_OBJECT (item), "row", GUINT_TO_POINTER (row));
g_object_set_data (G_OBJECT (item), "column", GUINT_TO_POINTER (col));
}
/* set new canvas scroll region */
sr_width = priv->n_cols * (MAX_ITEM_WIDTH + priv->col_spacing) - priv->col_spacing;
sr_height = row_offset + max_image_height + max_caption_height + EOG_WRAP_LIST_BORDER;
gnome_canvas_set_scroll_region (GNOME_CANVAS (wlist),
0.0, 0.0,
sr_width, sr_height);
#if COLLECTION_DEBUG
g_message ("do_item_rearrangement leaved");
#endif
}
static gboolean
do_update (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
gboolean layout_check_needed = FALSE;
gboolean item_rearrangement_needed = FALSE;
g_return_val_if_fail (wlist != NULL, FALSE);
g_return_val_if_fail (EOG_IS_WRAP_LIST (wlist), FALSE);
priv = wlist->priv;
/* handle global updates */
if (priv->global_update_hints [GLOBAL_SPACING_CHANGED] ||
priv->global_update_hints [GLOBAL_WIDGET_SIZE_CHANGED])
{
layout_check_needed = TRUE;
item_rearrangement_needed = TRUE;
priv->global_update_hints[GLOBAL_SPACING_CHANGED] = FALSE;
priv->global_update_hints[GLOBAL_WIDGET_SIZE_CHANGED] = FALSE;
}
else if (priv->global_update_hints [ITEM_SIZE_CHANGED]) {
item_rearrangement_needed = TRUE;
priv->global_update_hints [ITEM_SIZE_CHANGED] = FALSE;
}
if (layout_check_needed && do_layout_check (wlist))
item_rearrangement_needed = TRUE;
if (item_rearrangement_needed) {
do_item_rearrangement (wlist);
}
priv->idle_handler_id = -1;
return FALSE;
}
int
eog_wrap_list_get_n_selected (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
priv = wlist->priv;
return priv->n_selected_items;
}
EogImage*
eog_wrap_list_get_first_selected_image (EogWrapList *wlist)
{
EogImage *image = NULL;
EogWrapListPrivate *priv;
g_return_val_if_fail (EOG_IS_WRAP_LIST (wlist), NULL);
priv = wlist->priv;
if (priv->selected_items != NULL) {
EogCollectionItem *item;
item = EOG_COLLECTION_ITEM (priv->selected_items->data);
image = eog_collection_item_get_image (item);
}
return image;
}
GList*
eog_wrap_list_get_selected_images (EogWrapList *wlist)
{
EogWrapListPrivate *priv;
GList *list = NULL;
GList *it;
g_return_val_if_fail (EOG_IS_WRAP_LIST (wlist), NULL);
priv = wlist->priv;
for (it = priv->selected_items; it != NULL; it = it->next) {
EogCollectionItem *item = EOG_COLLECTION_ITEM (it->data);
list = g_list_prepend (list, eog_collection_item_get_image (item));
}
list = g_list_reverse (list);
return list;
}
/* eog_wrap_list_set_current_image
*
* This function makes sure that the corresponding item to image is in
* the list of selected items. Also it is marked as the last clicked
* item. If the flag deselect_other is TRUE than all other selected
* item will be deselected.
*/
void
eog_wrap_list_set_current_image (EogWrapList *wlist, EogImage *image, gboolean deselect_other)
{
EogWrapListPrivate *priv;
EogCollectionItem *item;
gboolean selection_changed = FALSE;
int pos;
g_return_if_fail (EOG_IS_WRAP_LIST (wlist));
g_return_if_fail (EOG_IS_IMAGE (image));
priv = wlist->priv;
/* Warning: We rely on the fact that the model list and the
view_order list are totally in sync wrt to the
sequence. AFAICS this is a valid assumption currently. */
pos = eog_image_list_get_pos_by_img (priv->model, image);
if (pos == -1) return;
item = g_list_nth_data (priv->view_order, pos);
priv->last_item_clicked = GNOME_CANVAS_ITEM (item);
if (deselect_other || (priv->n_selected_items == 1)) {
deselect_all (wlist);
}
selection_changed = set_select_status (wlist, item, TRUE);
if (selection_changed) {
g_signal_emit (G_OBJECT (wlist), eog_wrap_list_signals [SELECTION_CHANGED], 0);
}
ensure_item_is_visible (wlist, GNOME_CANVAS_ITEM (item));
}