Evolution consists of entirely too many small utility libraries, which increases linking and loading time, places a burden on higher layers of the application (e.g. modules) which has to remember to link to all the small in-tree utility libraries, and makes it difficult to generate API documentation for these utility libraries in one Gtk-Doc module. Merge the following utility libraries under the umbrella of libeutil, and enforce a single-include policy on libeutil so we can reorganize the files as desired without disrupting its pseudo-public API. libemail-utils/libemail-utils.la libevolution-utils/libevolution-utils.la filter/libfilter.la widgets/e-timezone-dialog/libetimezonedialog.la widgets/menus/libmenus.la widgets/misc/libemiscwidgets.la widgets/table/libetable.la widgets/text/libetext.la This also merges libedataserverui from the Evolution-Data-Server module, since Evolution is its only consumer nowadays, and I'd like to make some improvements to those APIs without concern for backward-compatibility. And finally, start a Gtk-Doc module for libeutil. It's going to be a project just getting all the symbols _listed_ much less _documented_. But the skeletal structure is in place and I'm off to a good start.
1733 lines
47 KiB
C
1733 lines
47 KiB
C
/* -*- 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 <http://www.gnu.org/licenses/>
|
|
*
|
|
*
|
|
* Authors:
|
|
* Chris Lahey <clahey@ximian.com>
|
|
*
|
|
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "e-reflow.h"
|
|
|
|
#include <math.h>
|
|
#include <string.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#include "e-canvas-utils.h"
|
|
#include "e-canvas.h"
|
|
#include "e-marshal.h"
|
|
#include "e-selection-model-simple.h"
|
|
#include "e-text.h"
|
|
#include "e-unicode.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, cairo_t *cr,
|
|
gint x, gint y, gint width, gint height);
|
|
static void e_reflow_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags);
|
|
static GnomeCanvasItem *e_reflow_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy);
|
|
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)
|
|
|
|
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_scrollable_get_hadjustment (GTK_SCROLLABLE (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_scrollable_get_hadjustment (GTK_SCROLLABLE (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 (; list && 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,
|
|
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 = g_object_ref (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 = g_object_ref (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);
|
|
}
|
|
|
|
#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 property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GnomeCanvasItem *item;
|
|
EReflow *reflow;
|
|
|
|
item = GNOME_CANVAS_ITEM (object);
|
|
reflow = E_REFLOW (object);
|
|
|
|
switch (property_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_scrollable_get_hadjustment (GTK_SCROLLABLE (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 property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
EReflow *reflow;
|
|
|
|
reflow = E_REFLOW (object);
|
|
|
|
switch (property_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, property_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_scrollable_get_hadjustment (GTK_SCROLLABLE (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);
|
|
|
|
g_object_unref (reflow->arrow_cursor);
|
|
g_object_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->device,
|
|
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_scrollable_get_hadjustment (GTK_SCROLLABLE (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_scrollable_get_hadjustment (GTK_SCROLLABLE (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_scrollable_get_hadjustment (GTK_SCROLLABLE (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_scrollable_get_hadjustment (GTK_SCROLLABLE (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,
|
|
cairo_t *cr,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
GtkStyleContext *style_context;
|
|
GtkWidget *widget;
|
|
gint x_rect, y_rect, width_rect, height_rect;
|
|
gdouble running_width;
|
|
EReflow *reflow = E_REFLOW (item);
|
|
GdkRGBA color;
|
|
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, cr, 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);
|
|
|
|
widget = GTK_WIDGET (item->canvas);
|
|
style_context = gtk_widget_get_style_context (widget);
|
|
|
|
cairo_save (cr);
|
|
|
|
gtk_style_context_get_background_color (
|
|
style_context, GTK_STATE_FLAG_ACTIVE, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
for (; i < reflow->column_count; i++) {
|
|
if (running_width > x + width)
|
|
break;
|
|
x_rect = running_width;
|
|
|
|
gtk_render_background (
|
|
style_context, cr,
|
|
(gdouble) x_rect - x,
|
|
(gdouble) y_rect - y,
|
|
(gdouble) width_rect,
|
|
(gdouble) height_rect);
|
|
|
|
running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
|
|
if (reflow->column_drag) {
|
|
GtkAdjustment *adjustment;
|
|
GtkLayout *layout;
|
|
gdouble value;
|
|
gint start_line;
|
|
|
|
layout = GTK_LAYOUT (item->canvas);
|
|
adjustment = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (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);
|
|
|
|
cairo_save (cr);
|
|
|
|
gtk_style_context_get_color (
|
|
style_context, GTK_STATE_FLAG_NORMAL, &color);
|
|
gdk_cairo_set_source_rgba (cr, &color);
|
|
|
|
for (; i < reflow->column_count; i++) {
|
|
if (running_width > x + width)
|
|
break;
|
|
x_rect = running_width;
|
|
cairo_rectangle (
|
|
cr,
|
|
x_rect - x,
|
|
y_rect - y,
|
|
width_rect - 1,
|
|
height_rect - 1);
|
|
cairo_fill (cr);
|
|
running_width += E_REFLOW_DIVIDER_WIDTH + E_REFLOW_BORDER_WIDTH + column_width + E_REFLOW_BORDER_WIDTH;
|
|
}
|
|
|
|
cairo_restore (cr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
e_reflow_update (GnomeCanvasItem *item,
|
|
const cairo_matrix_t *i2c,
|
|
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, i2c, 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_scrollable_get_hadjustment (GTK_SCROLLABLE (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 GnomeCanvasItem *
|
|
e_reflow_point (GnomeCanvasItem *item,
|
|
gdouble x,
|
|
gdouble y,
|
|
gint cx,
|
|
gint cy)
|
|
{
|
|
GnomeCanvasItem *child = NULL;
|
|
|
|
if (GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point)
|
|
child = GNOME_CANVAS_ITEM_CLASS (e_reflow_parent_class)->point (item, x, y, cx, cy);
|
|
|
|
return child ? child : item;
|
|
#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 *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
GnomeCanvasItemClass *item_class;
|
|
|
|
object_class = (GObjectClass *) class;
|
|
item_class = (GnomeCanvasItemClass *) class;
|
|
|
|
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;
|
|
|
|
class->selection_event = e_reflow_selection_event_real;
|
|
class->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_TYPE_REFLOW_MODEL,
|
|
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 | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
|
|
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);
|
|
}
|