/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Chris Lahey * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) */ #include #include #include #include #include #include "text/e-text.h" #include #include "e-util/e-util.h" #include "e-util/e-unicode.h" #include "misc/e-canvas.h" #include "misc/e-canvas-utils.h" #include "e-reflow.h" #include "misc/e-selection-model-simple.h" static gboolean e_reflow_event (GnomeCanvasItem *item, GdkEvent *event); static void e_reflow_realize (GnomeCanvasItem *item); static void e_reflow_unrealize (GnomeCanvasItem *item); static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable, gint x, gint y, gint width, gint height); static void e_reflow_update (GnomeCanvasItem *item, gdouble affine[6], ArtSVP *clip_path, gint flags); static gdouble e_reflow_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy, GnomeCanvasItem **actual_item); static void e_reflow_reflow (GnomeCanvasItem *item, gint flags); static void set_empty (EReflow *reflow); static void e_reflow_resize_children (GnomeCanvasItem *item); #define E_REFLOW_DIVIDER_WIDTH 2 #define E_REFLOW_BORDER_WIDTH 7 #define E_REFLOW_FULL_GUTTER (E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH * 2) G_DEFINE_TYPE (EReflow, e_reflow, GNOME_TYPE_CANVAS_GROUP) /* The arguments we take */ enum { PROP_0, PROP_MINIMUM_WIDTH, PROP_WIDTH, PROP_HEIGHT, PROP_EMPTY_MESSAGE, PROP_MODEL, PROP_COLUMN_WIDTH }; enum { SELECTION_EVENT, COLUMN_WIDTH_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0, }; static GHashTable * er_create_cmp_cache (gpointer user_data) { EReflow *reflow = user_data; return e_reflow_model_create_cmp_cache (reflow->model); } static gint er_compare (gint i1, gint i2, GHashTable *cmp_cache, gpointer user_data) { EReflow *reflow = user_data; return e_reflow_model_compare (reflow->model, i1, i2, cmp_cache); } static gint e_reflow_pick_line (EReflow *reflow, gdouble x) { x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; x /= reflow->column_width + E_REFLOW_FULL_GUTTER; return x; } static gint er_find_item (EReflow *reflow, GnomeCanvasItem *item) { gint i; for (i = 0; i < reflow->count; i++) { if (reflow->items[i] == item) return i; } return -1; } static void e_reflow_resize_children (GnomeCanvasItem *item) { EReflow *reflow; gint i; gint count; reflow = E_REFLOW (item); count = reflow->count; for (i = 0; i < count; i++) { if (reflow->items[i]) gnome_canvas_item_set (reflow->items[i], "width", (gdouble) reflow->column_width, NULL); } } static inline void e_reflow_update_selection_row (EReflow *reflow, gint row) { if (reflow->items[row]) { g_object_set (reflow->items[row], "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), row), NULL); } else if (e_selection_model_is_row_selected (E_SELECTION_MODEL (reflow->selection), row)) { reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow)); g_object_set (reflow->items[row], "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), row), "width", (gdouble) reflow->column_width, NULL); } } static void e_reflow_update_selection (EReflow *reflow) { gint i; gint count; count = reflow->count; for (i = 0; i < count; i++) { e_reflow_update_selection_row (reflow, i); } } static void selection_changed (ESelectionModel *selection, EReflow *reflow) { e_reflow_update_selection (reflow); } static void selection_row_changed (ESelectionModel *selection, gint row, EReflow *reflow) { e_reflow_update_selection_row (reflow, row); } static gboolean do_adjustment (gpointer user_data) { gint row; GtkLayout *layout; GtkAdjustment *adjustment; gdouble page_size; gdouble value, min_value, max_value; EReflow *reflow = user_data; row = reflow->cursor_row; if (row == -1) return FALSE; layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); page_size = gtk_adjustment_get_page_size (adjustment); if ((!reflow->items) || (!reflow->items[row])) return TRUE; min_value = reflow->items[row]->x2 - page_size; max_value = reflow->items[row]->x1; if (value < min_value) value = min_value; if (value > max_value) value = max_value; if (value != gtk_adjustment_get_value (adjustment)) gtk_adjustment_set_value (adjustment, value); reflow->do_adjustment_idle_id = 0; return FALSE; } static void cursor_changed (ESelectionModel *selection, gint row, gint col, EReflow *reflow) { gint count = reflow->count; gint old_cursor = reflow->cursor_row; if (old_cursor < count && old_cursor >= 0) { if (reflow->items[old_cursor]) { g_object_set (reflow->items[old_cursor], "has_cursor", FALSE, NULL); } } reflow->cursor_row = row; if (row < count && row >= 0) { if (reflow->items[row]) { g_object_set (reflow->items[row], "has_cursor", TRUE, NULL); } else { reflow->items[row] = e_reflow_model_incarnate (reflow->model, row, GNOME_CANVAS_GROUP (reflow)); g_object_set (reflow->items[row], "has_cursor", TRUE, "width", (gdouble) reflow->column_width, NULL); } } if (reflow->do_adjustment_idle_id == 0) reflow->do_adjustment_idle_id = g_idle_add (do_adjustment, reflow); } static void incarnate (EReflow *reflow) { gint column_width; gint first_column; gint last_column; gint first_cell; gint last_cell; gint i; GtkLayout *layout; GtkAdjustment *adjustment; gdouble value; gdouble page_size; layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (reflow)->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); page_size = gtk_adjustment_get_page_size (adjustment); column_width = reflow->column_width; first_column = value - 1 + E_REFLOW_BORDER_WIDTH; first_column /= column_width + E_REFLOW_FULL_GUTTER; last_column = value + page_size + 1 - E_REFLOW_BORDER_WIDTH - E_REFLOW_DIVIDER_WIDTH; last_column /= column_width + E_REFLOW_FULL_GUTTER; last_column++; if (first_column >= 0 && first_column < reflow->column_count) first_cell = reflow->columns[first_column]; else first_cell = 0; if (last_column >= 0 && last_column < reflow->column_count) last_cell = reflow->columns[last_column]; else last_cell = reflow->count; for (i = first_cell; i < last_cell; i++) { gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); if (reflow->items[unsorted] == NULL) { if (reflow->model) { reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow)); g_object_set (reflow->items[unsorted], "selected", e_selection_model_is_row_selected(E_SELECTION_MODEL(reflow->selection), unsorted), "width", (gdouble) reflow->column_width, NULL); } } } reflow->incarnate_idle_id = 0; } static gboolean invoke_incarnate (gpointer user_data) { EReflow *reflow = user_data; incarnate (reflow); return FALSE; } static void queue_incarnate (EReflow *reflow) { if (reflow->incarnate_idle_id == 0) reflow->incarnate_idle_id = g_idle_add_full (25, invoke_incarnate, reflow, NULL); } static void reflow_columns (EReflow *reflow) { GSList *list; gint count; gint start; gint i; gint column_count, column_start; gdouble running_height; if (reflow->reflow_from_column <= 1) { start = 0; column_count = 1; column_start = 0; } else { /* we start one column before the earliest new entry, so we can handle the case where the new entry is inserted at the start of the column */ column_start = reflow->reflow_from_column - 1; start = reflow->columns[column_start]; column_count = column_start + 1; } list = NULL; running_height = E_REFLOW_BORDER_WIDTH; count = reflow->count - start; for (i = start; i < count; i++) { gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); if (i != 0 && running_height + reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH > reflow->height) { list = g_slist_prepend (list, GINT_TO_POINTER (i)); column_count++; running_height = E_REFLOW_BORDER_WIDTH * 2 + reflow->heights[unsorted]; } else running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH; } reflow->column_count = column_count; reflow->columns = g_renew (int, reflow->columns, column_count); column_count--; for (; column_count > column_start; column_count--) { GSList *to_free; reflow->columns[column_count] = GPOINTER_TO_INT (list->data); to_free = list; list = list->next; g_slist_free_1 (to_free); } reflow->columns[column_start] = start; queue_incarnate (reflow); reflow->need_reflow_columns = FALSE; reflow->reflow_from_column = -1; } static void item_changed (EReflowModel *model, gint i, EReflow *reflow) { if (i < 0 || i >= reflow->count) return; reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); if (reflow->items[i] != NULL) e_reflow_model_reincarnate (model, i, reflow->items[i]); e_sorter_array_clean (reflow->sorter); reflow->reflow_from_column = -1; reflow->need_reflow_columns = TRUE; e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow)); } static void item_removed (EReflowModel *model, gint i, EReflow *reflow) { gint c; gint sorted; if (i < 0 || i >= reflow->count) return; sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i); for (c = reflow->column_count - 1; c >= 0; c--) { gint start_of_column = reflow->columns[c]; if (start_of_column <= sorted) { if (reflow->reflow_from_column == -1 || reflow->reflow_from_column > c) { reflow->reflow_from_column = c; } break; } } if (reflow->items[i]) g_object_run_dispose (G_OBJECT (reflow->items[i])); memmove (reflow->heights + i, reflow->heights + i + 1, (reflow->count - i - 1) * sizeof (gint)); memmove (reflow->items + i, reflow->items + i + 1, (reflow->count - i - 1) * sizeof (GnomeCanvasItem *)); reflow->count--; reflow->heights[reflow->count] = 0; reflow->items[reflow->count] = NULL; reflow->need_reflow_columns = TRUE; set_empty (reflow); e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow)); e_sorter_array_set_count (reflow->sorter, reflow->count); e_selection_model_simple_delete_rows (E_SELECTION_MODEL_SIMPLE (reflow->selection), i, 1); } static void items_inserted (EReflowModel *model, gint position, gint count, EReflow *reflow) { gint i, oldcount; if (position < 0 || position > reflow->count) return; oldcount = reflow->count; reflow->count += count; if (reflow->count > reflow->allocated_count) { while (reflow->count > reflow->allocated_count) reflow->allocated_count += 256; reflow->heights = g_renew (int, reflow->heights, reflow->allocated_count); reflow->items = g_renew (GnomeCanvasItem *, reflow->items, reflow->allocated_count); } memmove (reflow->heights + position + count, reflow->heights + position, (reflow->count - position - count) * sizeof (gint)); memmove (reflow->items + position + count, reflow->items + position, (reflow->count - position - count) * sizeof (GnomeCanvasItem *)); for (i = position; i < position + count; i++) { reflow->items[i] = NULL; reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); } e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), reflow->count); if (position == oldcount) e_sorter_array_append (reflow->sorter, count); else e_sorter_array_set_count (reflow->sorter, reflow->count); for (i = position; i < position + count; i++) { gint sorted = e_sorter_model_to_sorted (E_SORTER (reflow->sorter), i); gint c; for (c = reflow->column_count - 1; c >= 0; c--) { gint start_of_column = reflow->columns[c]; if (start_of_column <= sorted) { if (reflow->reflow_from_column == -1 || reflow->reflow_from_column > c) { reflow->reflow_from_column = c; } break; } } } reflow->need_reflow_columns = TRUE; set_empty (reflow); e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow)); } static void model_changed (EReflowModel *model, EReflow *reflow) { gint i; gint count; gint oldcount; count = reflow->count; oldcount = count; for (i = 0; i < count; i++) { if (reflow->items[i]) g_object_run_dispose (G_OBJECT (reflow->items[i])); } g_free (reflow->items); g_free (reflow->heights); reflow->count = e_reflow_model_count (model); reflow->allocated_count = reflow->count; reflow->items = g_new (GnomeCanvasItem *, reflow->count); reflow->heights = g_new (int, reflow->count); count = reflow->count; for (i = 0; i < count; i++) { reflow->items[i] = NULL; reflow->heights[i] = e_reflow_model_height (reflow->model, i, GNOME_CANVAS_GROUP (reflow)); } e_selection_model_simple_set_row_count (E_SELECTION_MODEL_SIMPLE (reflow->selection), count); e_sorter_array_set_count (reflow->sorter, reflow->count); reflow->need_reflow_columns = TRUE; if (oldcount > reflow->count) reflow_columns (reflow); set_empty (reflow); e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow)); } static void comparison_changed (EReflowModel *model, EReflow *reflow) { e_sorter_array_clean (reflow->sorter); reflow->reflow_from_column = -1; reflow->need_reflow_columns = TRUE; e_canvas_item_request_reflow (GNOME_CANVAS_ITEM (reflow)); } static void set_empty (EReflow *reflow) { if (reflow->count == 0) { if (reflow->empty_text) { if (reflow->empty_message) { gnome_canvas_item_set (reflow->empty_text, "width", reflow->minimum_width, "text", reflow->empty_message, NULL); e_canvas_item_move_absolute (reflow->empty_text, reflow->minimum_width / 2, 0); } else { g_object_run_dispose (G_OBJECT (reflow->empty_text)); reflow->empty_text = NULL; } } else { if (reflow->empty_message) { reflow->empty_text = gnome_canvas_item_new (GNOME_CANVAS_GROUP (reflow), e_text_get_type (), "width", reflow->minimum_width, "clip", TRUE, "use_ellipsis", TRUE, "justification", GTK_JUSTIFY_CENTER, "text", reflow->empty_message, "draw_background", FALSE, NULL); e_canvas_item_move_absolute (reflow->empty_text, reflow->minimum_width / 2, 0); } } } else { if (reflow->empty_text) { g_object_run_dispose (G_OBJECT (reflow->empty_text)); reflow->empty_text = NULL; } } } static void disconnect_model (EReflow *reflow) { if (reflow->model == NULL) return; g_signal_handler_disconnect (reflow->model, reflow->model_changed_id); g_signal_handler_disconnect (reflow->model, reflow->comparison_changed_id); g_signal_handler_disconnect (reflow->model, reflow->model_items_inserted_id); g_signal_handler_disconnect (reflow->model, reflow->model_item_removed_id); g_signal_handler_disconnect (reflow->model, reflow->model_item_changed_id); g_object_unref (reflow->model); reflow->model_changed_id = 0; reflow->comparison_changed_id = 0; reflow->model_items_inserted_id = 0; reflow->model_item_removed_id = 0; reflow->model_item_changed_id = 0; reflow->model = NULL; } static void disconnect_selection (EReflow *reflow) { if (reflow->selection == NULL) return; g_signal_handler_disconnect (reflow->selection, reflow->selection_changed_id); g_signal_handler_disconnect (reflow->selection, reflow->selection_row_changed_id); g_signal_handler_disconnect (reflow->selection, reflow->cursor_changed_id); g_object_unref (reflow->selection); reflow->selection_changed_id = 0; reflow->selection_row_changed_id = 0; reflow->cursor_changed_id = 0; reflow->selection = NULL; } static void connect_model (EReflow *reflow, EReflowModel *model) { if (reflow->model != NULL) disconnect_model (reflow); if (model == NULL) return; reflow->model = model; g_object_ref (reflow->model); reflow->model_changed_id = g_signal_connect (reflow->model, "model_changed", G_CALLBACK (model_changed), reflow); reflow->comparison_changed_id = g_signal_connect (reflow->model, "comparison_changed", G_CALLBACK (comparison_changed), reflow); reflow->model_items_inserted_id = g_signal_connect (reflow->model, "model_items_inserted", G_CALLBACK (items_inserted), reflow); reflow->model_item_removed_id = g_signal_connect (reflow->model, "model_item_removed", G_CALLBACK (item_removed), reflow); reflow->model_item_changed_id = g_signal_connect (reflow->model, "model_item_changed", G_CALLBACK (item_changed), reflow); model_changed (model, reflow); } static void adjustment_changed (GtkAdjustment *adjustment, EReflow *reflow) { queue_incarnate (reflow); } static void disconnect_adjustment (EReflow *reflow) { if (reflow->adjustment == NULL) return; g_signal_handler_disconnect (reflow->adjustment, reflow->adjustment_changed_id); g_signal_handler_disconnect (reflow->adjustment, reflow->adjustment_value_changed_id); g_object_unref (reflow->adjustment); reflow->adjustment_changed_id = 0; reflow->adjustment_value_changed_id = 0; reflow->adjustment = NULL; } static void connect_adjustment (EReflow *reflow, GtkAdjustment *adjustment) { if (reflow->adjustment != NULL) disconnect_adjustment (reflow); if (adjustment == NULL) return; reflow->adjustment = adjustment; reflow->adjustment_changed_id = g_signal_connect (adjustment, "changed", G_CALLBACK (adjustment_changed), reflow); reflow->adjustment_value_changed_id = g_signal_connect (adjustment, "value_changed", G_CALLBACK (adjustment_changed), reflow); g_object_ref (adjustment); } #if 0 static void set_scroll_adjustments (GtkLayout *layout, GtkAdjustment *hadj, GtkAdjustment *vadj, EReflow *reflow) { connect_adjustment (reflow, hadj); } static void connect_set_adjustment (EReflow *reflow) { reflow->set_scroll_adjustments_id = g_signal_connect (GNOME_CANVAS_ITEM (reflow)->canvas, "set_scroll_adjustments", G_CALLBACK (set_scroll_adjustments), reflow); } #endif static void disconnect_set_adjustment (EReflow *reflow) { if (reflow->set_scroll_adjustments_id != 0) { g_signal_handler_disconnect (GNOME_CANVAS_ITEM (reflow)->canvas, reflow->set_scroll_adjustments_id); reflow->set_scroll_adjustments_id = 0; } } static void column_width_changed (EReflow *reflow) { g_signal_emit (reflow, signals[COLUMN_WIDTH_CHANGED], 0, reflow->column_width); } /* Virtual functions */ static void e_reflow_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GnomeCanvasItem *item; EReflow *reflow; item = GNOME_CANVAS_ITEM (object); reflow = E_REFLOW (object); switch (prop_id) { case PROP_HEIGHT: reflow->height = g_value_get_double (value); reflow->need_reflow_columns = TRUE; e_canvas_item_request_reflow (item); break; case PROP_MINIMUM_WIDTH: reflow->minimum_width = g_value_get_double (value); if (item->flags & GNOME_CANVAS_ITEM_REALIZED) set_empty (reflow); e_canvas_item_request_reflow (item); break; case PROP_EMPTY_MESSAGE: g_free (reflow->empty_message); reflow->empty_message = g_strdup (g_value_get_string (value)); if (item->flags & GNOME_CANVAS_ITEM_REALIZED) set_empty (reflow); break; case PROP_MODEL: connect_model (reflow, (EReflowModel *) g_value_get_object (value)); break; case PROP_COLUMN_WIDTH: if (reflow->column_width != g_value_get_double (value)) { GtkLayout *layout; GtkAdjustment *adjustment; gdouble old_width = reflow->column_width; gdouble step_increment; gdouble page_size; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); page_size = gtk_adjustment_get_page_size (adjustment); reflow->column_width = g_value_get_double (value); step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; gtk_adjustment_set_step_increment ( adjustment, step_increment); gtk_adjustment_set_page_increment ( adjustment, page_size - step_increment); e_reflow_resize_children (item); e_canvas_item_request_reflow (item); reflow->need_column_resize = TRUE; gnome_canvas_item_request_update (item); if (old_width != reflow->column_width) column_width_changed (reflow); } break; } } static void e_reflow_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { EReflow *reflow; reflow = E_REFLOW (object); switch (prop_id) { case PROP_MINIMUM_WIDTH: g_value_set_double (value, reflow->minimum_width); break; case PROP_WIDTH: g_value_set_double (value, reflow->width); break; case PROP_HEIGHT: g_value_set_double (value, reflow->height); break; case PROP_EMPTY_MESSAGE: g_value_set_string (value, reflow->empty_message); break; case PROP_MODEL: g_value_set_object (value, reflow->model); break; case PROP_COLUMN_WIDTH: g_value_set_double (value, reflow->column_width); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void e_reflow_dispose (GObject *object) { EReflow *reflow = E_REFLOW (object); g_free (reflow->items); g_free (reflow->heights); g_free (reflow->columns); reflow->items = NULL; reflow->heights = NULL; reflow->columns = NULL; reflow->count = 0; reflow->allocated_count = 0; if (reflow->incarnate_idle_id) g_source_remove (reflow->incarnate_idle_id); reflow->incarnate_idle_id = 0; if (reflow->do_adjustment_idle_id) g_source_remove (reflow->do_adjustment_idle_id); reflow->do_adjustment_idle_id = 0; disconnect_model (reflow); disconnect_selection (reflow); g_free (reflow->empty_message); reflow->empty_message = NULL; if (reflow->sorter) { g_object_unref (reflow->sorter); reflow->sorter = NULL; } G_OBJECT_CLASS (e_reflow_parent_class)->dispose (object); } static void e_reflow_realize (GnomeCanvasItem *item) { EReflow *reflow; GtkAdjustment *adjustment; gdouble page_increment; gdouble step_increment; gdouble page_size; gint count; gint i; reflow = E_REFLOW (item); if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->realize) (* GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->realize) (item); reflow->arrow_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); reflow->default_cursor = gdk_cursor_new (GDK_LEFT_PTR); count = reflow->count; for (i = 0; i < count; i++) { if (reflow->items[i]) gnome_canvas_item_set (reflow->items[i], "width", reflow->column_width, NULL); } set_empty (reflow); reflow->need_reflow_columns = TRUE; e_canvas_item_request_reflow (item); adjustment = gtk_layout_get_hadjustment (GTK_LAYOUT (item->canvas)); #if 0 connect_set_adjustment (reflow); #endif connect_adjustment (reflow, adjustment); page_size = gtk_adjustment_get_page_size (adjustment); step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; page_increment = page_size - step_increment; gtk_adjustment_set_step_increment (adjustment, step_increment); gtk_adjustment_set_page_increment (adjustment, page_increment); } static void e_reflow_unrealize (GnomeCanvasItem *item) { EReflow *reflow; reflow = E_REFLOW (item); gdk_cursor_unref (reflow->arrow_cursor); gdk_cursor_unref (reflow->default_cursor); reflow->arrow_cursor = NULL; reflow->default_cursor = NULL; g_free (reflow->columns); reflow->columns = NULL; disconnect_set_adjustment (reflow); disconnect_adjustment (reflow); if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->unrealize) (* GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->unrealize) (item); } static gboolean e_reflow_event (GnomeCanvasItem *item, GdkEvent *event) { EReflow *reflow; gint return_val = FALSE; reflow = E_REFLOW (item); switch (event->type) { case GDK_KEY_PRESS: return_val = e_selection_model_key_press (reflow->selection, (GdkEventKey *) event); break; #if 0 if (event->key.keyval == GDK_Tab || event->key.keyval == GDK_KEY_KP_Tab || event->key.keyval == GDK_ISO_Left_Tab) { gint i; gint count; count = reflow->count; for (i = 0; i < count; i++) { gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); GnomeCanvasItem *item = reflow->items[unsorted]; EFocus has_focus; if (item) { g_object_get (item, "has_focus", &has_focus, NULL); if (has_focus) { if (event->key.state & GDK_SHIFT_MASK) { if (i == 0) return FALSE; i--; } else { if (i == count - 1) return FALSE; i++; } unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); if (reflow->items[unsorted] == NULL) { reflow->items[unsorted] = e_reflow_model_incarnate (reflow->model, unsorted, GNOME_CANVAS_GROUP (reflow)); } item = reflow->items[unsorted]; gnome_canvas_item_set (item, "has_focus", (event->key.state & GDK_SHIFT_MASK) ? E_FOCUS_END : E_FOCUS_START, NULL); return TRUE; } } } } #endif case GDK_BUTTON_PRESS: switch (event->button.button) { case 1: { GdkEventButton *button = (GdkEventButton *) event; gdouble n_x; n_x = button->x; n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); if (button->y >= E_REFLOW_BORDER_WIDTH && button->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) { /* don't allow to drag the first line*/ if (e_reflow_pick_line (reflow, button->x) == 0) return TRUE; reflow->which_column_dragged = e_reflow_pick_line (reflow, button->x); reflow->start_x = reflow->which_column_dragged * (reflow->column_width + E_REFLOW_FULL_GUTTER) - E_REFLOW_DIVIDER_WIDTH / 2; reflow->temp_column_width = reflow->column_width; reflow->column_drag = TRUE; gnome_canvas_item_grab (item, GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK, reflow->arrow_cursor, button->time); reflow->previous_temp_column_width = -1; reflow->need_column_resize = TRUE; gnome_canvas_item_request_update (item); return TRUE; } } break; case 4: { GtkLayout *layout; GtkAdjustment *adjustment; gdouble new_value; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); new_value = gtk_adjustment_get_value (adjustment); new_value -= gtk_adjustment_get_step_increment (adjustment); gtk_adjustment_set_value (adjustment, new_value); } break; case 5: { GtkLayout *layout; GtkAdjustment *adjustment; gdouble new_value; gdouble page_size; gdouble upper; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); new_value = gtk_adjustment_get_value (adjustment); new_value += gtk_adjustment_get_step_increment (adjustment); upper = gtk_adjustment_get_upper (adjustment); page_size = gtk_adjustment_get_page_size (adjustment); if (new_value > upper - page_size) new_value = upper - page_size; gtk_adjustment_set_value (adjustment, new_value); } break; } break; case GDK_BUTTON_RELEASE: if (reflow->column_drag) { gdouble old_width = reflow->column_width; GdkEventButton *button = (GdkEventButton *) event; GtkAdjustment *adjustment; GtkLayout *layout; gdouble value; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); reflow->temp_column_width = reflow->column_width + (button->x - reflow->start_x)/(reflow->which_column_dragged - e_reflow_pick_line (reflow, value)); if (reflow->temp_column_width < 50) reflow->temp_column_width = 50; reflow->column_drag = FALSE; if (old_width != reflow->temp_column_width) { gdouble page_increment; gdouble step_increment; gdouble page_size; page_size = gtk_adjustment_get_page_size (adjustment); gtk_adjustment_set_value (adjustment, value + e_reflow_pick_line (reflow, value) * (reflow->temp_column_width - reflow->column_width)); reflow->column_width = reflow->temp_column_width; step_increment = (reflow->column_width + E_REFLOW_FULL_GUTTER) / 2; page_increment = page_size - step_increment; gtk_adjustment_set_step_increment (adjustment, step_increment); gtk_adjustment_set_page_increment (adjustment, page_increment); e_reflow_resize_children (item); e_canvas_item_request_reflow (item); gnome_canvas_request_redraw (item->canvas, 0, 0, reflow->width, reflow->height); column_width_changed (reflow); } reflow->need_column_resize = TRUE; gnome_canvas_item_request_update (item); gnome_canvas_item_ungrab (item, button->time); return TRUE; } break; case GDK_MOTION_NOTIFY: if (reflow->column_drag) { gdouble old_width = reflow->temp_column_width; GdkEventMotion *motion = (GdkEventMotion *) event; GtkAdjustment *adjustment; GtkLayout *layout; gdouble value; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); reflow->temp_column_width = reflow->column_width + (motion->x - reflow->start_x)/(reflow->which_column_dragged - e_reflow_pick_line (reflow, value)); if (reflow->temp_column_width < 50) reflow->temp_column_width = 50; if (old_width != reflow->temp_column_width) { reflow->need_column_resize = TRUE; gnome_canvas_item_request_update (item); } return TRUE; } else { GdkEventMotion *motion = (GdkEventMotion *) event; GdkWindow *window; gdouble n_x; n_x = motion->x; n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); window = gtk_widget_get_window (GTK_WIDGET (item->canvas)); if (motion->y >= E_REFLOW_BORDER_WIDTH && motion->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) { if (reflow->default_cursor_shown) { gdk_window_set_cursor (window, reflow->arrow_cursor); reflow->default_cursor_shown = FALSE; } } else if (!reflow->default_cursor_shown) { gdk_window_set_cursor (window, reflow->default_cursor); reflow->default_cursor_shown = TRUE; } } break; case GDK_ENTER_NOTIFY: if (!reflow->column_drag) { GdkEventCrossing *crossing = (GdkEventCrossing *) event; GdkWindow *window; gdouble n_x; n_x = crossing->x; n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); window = gtk_widget_get_window (GTK_WIDGET (item->canvas)); if (crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER) { if (reflow->default_cursor_shown) { gdk_window_set_cursor (window, reflow->arrow_cursor); reflow->default_cursor_shown = FALSE; } } } break; case GDK_LEAVE_NOTIFY: if (!reflow->column_drag) { GdkEventCrossing *crossing = (GdkEventCrossing *) event; GdkWindow *window; gdouble n_x; n_x = crossing->x; n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; n_x = fmod (n_x,(reflow->column_width + E_REFLOW_FULL_GUTTER)); window = gtk_widget_get_window (GTK_WIDGET (item->canvas)); if (!( crossing->y >= E_REFLOW_BORDER_WIDTH && crossing->y <= reflow->height - E_REFLOW_BORDER_WIDTH && n_x < E_REFLOW_FULL_GUTTER )) { if (!reflow->default_cursor_shown) { gdk_window_set_cursor (window, reflow->default_cursor); reflow->default_cursor_shown = TRUE; } } } break; default: break; } if (return_val) return return_val; else if (GNOME_CANVAS_ITEM_CLASS ( e_reflow_parent_class )->event) return (* GNOME_CANVAS_ITEM_CLASS ( e_reflow_parent_class )->event) (item, event); else return FALSE; } static void e_reflow_draw (GnomeCanvasItem *item, GdkDrawable *drawable, gint x, gint y, gint width, gint height) { GtkStyle *style; gint x_rect, y_rect, width_rect, height_rect; gdouble running_width; EReflow *reflow = E_REFLOW (item); gint i; gdouble column_width; if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->draw) GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->draw (item, drawable, x, y, width, height); column_width = reflow->column_width; running_width = E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; y_rect = E_REFLOW_BORDER_WIDTH; width_rect = E_REFLOW_DIVIDER_WIDTH; height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); /* Compute first column to draw. */ i = x; i /= column_width + E_REFLOW_FULL_GUTTER; running_width += i * (column_width + E_REFLOW_FULL_GUTTER); style = gtk_widget_get_style (GTK_WIDGET (item->canvas)); for (; i < reflow->column_count; i++) { if (running_width > x + width) break; x_rect = running_width; gtk_paint_flat_box (style, drawable, GTK_STATE_ACTIVE, GTK_SHADOW_NONE, NULL, GTK_WIDGET (item->canvas), "reflow", x_rect - x, y_rect - y, width_rect, height_rect); running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; } if (reflow->column_drag) { GtkAdjustment *adjustment; GtkLayout *layout; gdouble value; gint start_line; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); start_line = e_reflow_pick_line (reflow, value); i = x - start_line * (column_width + E_REFLOW_FULL_GUTTER); running_width = start_line * (column_width + E_REFLOW_FULL_GUTTER); column_width = reflow->temp_column_width; running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); i += start_line * (column_width + E_REFLOW_FULL_GUTTER); running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; y_rect = E_REFLOW_BORDER_WIDTH; width_rect = E_REFLOW_DIVIDER_WIDTH; height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); /* Compute first column to draw. */ i /= column_width + E_REFLOW_FULL_GUTTER; running_width += i * (column_width + E_REFLOW_FULL_GUTTER); for (; i < reflow->column_count; i++) { if (running_width > x + width) break; x_rect = running_width; gdk_draw_rectangle (drawable, style->fg_gc[GTK_STATE_NORMAL], TRUE, x_rect - x, y_rect - y, width_rect - 1, height_rect - 1); running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; } } } static void e_reflow_update (GnomeCanvasItem *item, gdouble affine[6], ArtSVP *clip_path, gint flags) { EReflow *reflow; gdouble x0, x1, y0, y1; reflow = E_REFLOW (item); if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->update) GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->update (item, affine, clip_path, flags); x0 = item->x1; y0 = item->y1; x1 = item->x2; y1 = item->y2; if (x1 < x0 + reflow->width) x1 = x0 + reflow->width; if (y1 < y0 + reflow->height) y1 = y0 + reflow->height; item->x2 = x1; item->y2 = y1; if (reflow->need_height_update) { x0 = item->x1; y0 = item->y1; x1 = item->x2; y1 = item->y2; if (x0 > 0) x0 = 0; if (y0 > 0) y0 = 0; if (x1 < E_REFLOW (item)->width) x1 = E_REFLOW (item)->width; if (x1 < E_REFLOW (item)->height) x1 = E_REFLOW (item)->height; gnome_canvas_request_redraw (item->canvas, x0, y0, x1, y1); reflow->need_height_update = FALSE; } else if (reflow->need_column_resize) { GtkLayout *layout; GtkAdjustment *adjustment; gint x_rect, y_rect, width_rect, height_rect; gint start_line; gdouble running_width; gint i; gdouble column_width; gdouble value; layout = GTK_LAYOUT (item->canvas); adjustment = gtk_layout_get_hadjustment (layout); value = gtk_adjustment_get_value (adjustment); start_line = e_reflow_pick_line (reflow, value); if (reflow->previous_temp_column_width != -1) { running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER); column_width = reflow->previous_temp_column_width; running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; y_rect = E_REFLOW_BORDER_WIDTH; width_rect = E_REFLOW_DIVIDER_WIDTH; height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); for ( i = 0; i < reflow->column_count; i++) { x_rect = running_width; gnome_canvas_request_redraw (item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect); running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; } } if (reflow->temp_column_width != -1) { running_width = start_line * (reflow->column_width + E_REFLOW_FULL_GUTTER); column_width = reflow->temp_column_width; running_width -= start_line * (column_width + E_REFLOW_FULL_GUTTER); running_width += E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; y_rect = E_REFLOW_BORDER_WIDTH; width_rect = E_REFLOW_DIVIDER_WIDTH; height_rect = reflow->height - (E_REFLOW_BORDER_WIDTH * 2); for ( i = 0; i < reflow->column_count; i++) { x_rect = running_width; gnome_canvas_request_redraw (item->canvas, x_rect, y_rect, x_rect + width_rect, y_rect + height_rect); running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH; } } reflow->previous_temp_column_width = reflow->temp_column_width; reflow->need_column_resize = FALSE; } } static double e_reflow_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy, GnomeCanvasItem **actual_item) { gdouble distance = 1; *actual_item = NULL; if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point) distance = GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point (item, x, y, cx, cy, actual_item); if ((gint) (distance * item->canvas->pixels_per_unit + 0.5) <= item->canvas->close_enough && *actual_item) return distance; *actual_item = item; return 0; #if 0 if (y >= E_REFLOW_BORDER_WIDTH && y <= reflow->height - E_REFLOW_BORDER_WIDTH) { gfloat n_x; n_x = x; n_x += E_REFLOW_BORDER_WIDTH + E_REFLOW_DIVIDER_WIDTH; n_x = fmod (n_x, (reflow->column_width + E_REFLOW_FULL_GUTTER)); if (n_x < E_REFLOW_FULL_GUTTER) { *actual_item = item; return 0; } } return distance; #endif } static void e_reflow_reflow ( GnomeCanvasItem *item, gint flags ) { EReflow *reflow = E_REFLOW (item); gdouble old_width; gdouble running_width; gdouble running_height; gint next_column; gint i; if (!(item->flags & GNOME_CANVAS_ITEM_REALIZED)) return; if (reflow->need_reflow_columns) { reflow_columns (reflow); } old_width = reflow->width; running_width = E_REFLOW_BORDER_WIDTH; running_height = E_REFLOW_BORDER_WIDTH; next_column = 1; for (i = 0; i < reflow->count; i++) { gint unsorted = e_sorter_sorted_to_model (E_SORTER (reflow->sorter), i); if (next_column < reflow->column_count && i == reflow->columns[next_column]) { running_height = E_REFLOW_BORDER_WIDTH; running_width += reflow->column_width + E_REFLOW_FULL_GUTTER; next_column++; } if (unsorted >= 0 && reflow->items[unsorted]) { e_canvas_item_move_absolute (GNOME_CANVAS_ITEM (reflow->items[unsorted]), (gdouble) running_width, (gdouble) running_height); running_height += reflow->heights[unsorted] + E_REFLOW_BORDER_WIDTH; } } reflow->width = running_width + reflow->column_width + E_REFLOW_BORDER_WIDTH; if (reflow->width < reflow->minimum_width) reflow->width = reflow->minimum_width; if (old_width != reflow->width) e_canvas_item_request_parent_reflow (item); } static gint e_reflow_selection_event_real (EReflow *reflow, GnomeCanvasItem *item, GdkEvent *event) { gint row; gint return_val = TRUE; switch (event->type) { case GDK_BUTTON_PRESS: switch (event->button.button) { case 1: /* Fall through. */ case 2: row = er_find_item (reflow, item); if (event->button.button == 1) { reflow->maybe_did_something = e_selection_model_maybe_do_something (reflow->selection, row, 0, event->button.state); reflow->maybe_in_drag = TRUE; } else { e_selection_model_do_something (reflow->selection, row, 0, event->button.state); } break; case 3: row = er_find_item (reflow, item); e_selection_model_right_click_down (reflow->selection, row, 0, 0); break; default: return_val = FALSE; break; } break; case GDK_BUTTON_RELEASE: if (event->button.button == 1) { if (reflow->maybe_in_drag) { reflow->maybe_in_drag = FALSE; if (!reflow->maybe_did_something) { row = er_find_item (reflow, item); e_selection_model_do_something (reflow->selection, row, 0, event->button.state); } } } break; case GDK_KEY_PRESS: return_val = e_selection_model_key_press (reflow->selection, (GdkEventKey *) event); break; default: return_val = FALSE; break; } return return_val; } static void e_reflow_class_init (EReflowClass *klass) { GObjectClass *object_class; GnomeCanvasItemClass *item_class; object_class = (GObjectClass*) klass; item_class = (GnomeCanvasItemClass *) klass; object_class->set_property = e_reflow_set_property; object_class->get_property = e_reflow_get_property; object_class->dispose = e_reflow_dispose; /* GnomeCanvasItem method overrides */ item_class->event = e_reflow_event; item_class->realize = e_reflow_realize; item_class->unrealize = e_reflow_unrealize; item_class->draw = e_reflow_draw; item_class->update = e_reflow_update; item_class->point = e_reflow_point; klass->selection_event = e_reflow_selection_event_real; klass->column_width_changed = NULL; g_object_class_install_property (object_class, PROP_MINIMUM_WIDTH, g_param_spec_double ("minimum_width", "Minimum width", "Minimum Width", 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_WIDTH, g_param_spec_double ("width", "Width", "Width", 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READABLE)); g_object_class_install_property (object_class, PROP_HEIGHT, g_param_spec_double ("height", "Height", "Height", 0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_EMPTY_MESSAGE, g_param_spec_string ("empty_message", "Empty message", "Empty message", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MODEL, g_param_spec_object ("model", "Reflow model", "Reflow model", E_REFLOW_MODEL_TYPE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_COLUMN_WIDTH, g_param_spec_double ("column_width", "Column width", "Column width", 0.0, G_MAXDOUBLE, 150.0, G_PARAM_READWRITE)); signals[SELECTION_EVENT] = g_signal_new ("selection_event", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EReflowClass, selection_event), NULL, NULL, e_marshal_INT__OBJECT_BOXED, G_TYPE_INT, 2, G_TYPE_OBJECT, GDK_TYPE_EVENT); signals[COLUMN_WIDTH_CHANGED] = g_signal_new ("column_width_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EReflowClass, column_width_changed), NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); } static void e_reflow_init (EReflow *reflow) { reflow->model = NULL; reflow->items = NULL; reflow->heights = NULL; reflow->count = 0; reflow->columns = NULL; reflow->column_count = 0; reflow->empty_text = NULL; reflow->empty_message = NULL; reflow->minimum_width = 10; reflow->width = 10; reflow->height = 10; reflow->column_width = 150; reflow->column_drag = FALSE; reflow->need_height_update = FALSE; reflow->need_column_resize = FALSE; reflow->need_reflow_columns = FALSE; reflow->maybe_did_something = FALSE; reflow->maybe_in_drag = FALSE; reflow->default_cursor_shown = TRUE; reflow->arrow_cursor = NULL; reflow->default_cursor = NULL; reflow->cursor_row = -1; reflow->incarnate_idle_id = 0; reflow->do_adjustment_idle_id = 0; reflow->set_scroll_adjustments_id = 0; reflow->selection = E_SELECTION_MODEL (e_selection_model_simple_new ()); reflow->sorter = e_sorter_array_new (er_create_cmp_cache, er_compare, reflow); g_object_set (reflow->selection, "sorter", reflow->sorter, NULL); reflow->selection_changed_id = g_signal_connect(reflow->selection, "selection_changed", G_CALLBACK (selection_changed), reflow); reflow->selection_row_changed_id = g_signal_connect(reflow->selection, "selection_row_changed", G_CALLBACK (selection_row_changed), reflow); reflow->cursor_changed_id = g_signal_connect(reflow->selection, "cursor_changed", G_CALLBACK (cursor_changed), reflow); e_canvas_item_set_reflow_callback (GNOME_CANVAS_ITEM (reflow), e_reflow_reflow); }