From e65ce47c209498fc592e9bfb2720abdc0d2ffb9c Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Sun, 25 Feb 2001 19:29:40 +0000 Subject: new abstract ECell subclass to be used as base class for popup ECells. 2001-02-25 Damon Chaplin * e-cell-popup.c: new abstract ECell subclass to be used as base class for popup ECells. * e-cell-combo.c: subclass of ECellPopup which pops up a simple list of strings. * e-table-item.c: Renamed eti_row_diff() to e_table_item_row_diff() and made public, since the ECellPopup subclasses need it. * Makefile.am: added e-cell-popup.[hc] and e-cell-combo.[hc] svn path=/trunk/; revision=8387 --- widgets/table/e-cell-combo.c | 571 +++++++++++++++++++++++++++++++++++++++++++ widgets/table/e-cell-combo.h | 62 +++++ widgets/table/e-cell-popup.c | 504 ++++++++++++++++++++++++++++++++++++++ widgets/table/e-cell-popup.h | 93 +++++++ widgets/table/e-table-item.c | 14 +- widgets/table/e-table-item.h | 3 + 6 files changed, 1240 insertions(+), 7 deletions(-) create mode 100644 widgets/table/e-cell-combo.c create mode 100644 widgets/table/e-cell-combo.h create mode 100644 widgets/table/e-cell-popup.c create mode 100644 widgets/table/e-cell-popup.h (limited to 'widgets/table') diff --git a/widgets/table/e-cell-combo.c b/widgets/table/e-cell-combo.c new file mode 100644 index 0000000000..8aa689937a --- /dev/null +++ b/widgets/table/e-cell-combo.c @@ -0,0 +1,571 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + * ECellCombo - a subclass of ECellPopup used to support popup lists like a + * GtkCombo widget. It only supports a basic popup list of strings at present, + * with no auto-completion. + */ + +/* + * Notes: (handling pointer grabs and GTK+ grabs is a nightmare!) + * + * o We must grab the pointer when we show the popup, so that if any buttons + * are pressed outside the application we hide the popup. + * + * o We have to be careful when popping up any widgets which also grab the + * pointer at some point, since we will lose our own pointer grab. + * When we pop up a list it will grab the pointer itself when an item is + * selected, and release the grab when the button is released. + * Fortunately we hide the popup at this point, so it isn't a problem. + * But for other types of widgets in the popup it could cause trouble. + * - I think GTK+ should provide help for this (nested pointer grabs?). + * + * o We must set the 'owner_events' flag of the pointer grab to TRUE so that + * pointer events get reported to all the application windows as normal. + * If we don't do this then the widgets in the popup may not work properly. + * + * o We must do a gtk_grab_add() so that we only allow events to go to the + * widgets within the popup (though some special events still get reported + * to the widget owning the window). Doing th gtk_grab_add() on the toplevel + * popup window should be fine. We can then check for any events that should + * close the popup, like the Escape key, or a button press outside the popup. + */ + +#include +#include +#include "gal/util/e-util.h" +#include "e-table-item.h" +#include "e-cell-combo.h" + + +/* The height to make the popup list if there aren't any items in it. */ +#define E_CELL_COMBO_LIST_EMPTY_HEIGHT 15 + + +static void e_cell_combo_class_init (GtkObjectClass *object_class); +static void e_cell_combo_init (ECellCombo *ecc); +static void e_cell_combo_destroy (GtkObject *object); + +static gint e_cell_combo_do_popup (ECellPopup *ecp, + GdkEvent *event); +static void e_cell_combo_show_popup (ECellCombo *ecc); +static void e_cell_combo_get_popup_pos (ECellCombo *ecc, + gint *x, + gint *y, + gint *height, + gint *width); + +static gint e_cell_combo_button_press (GtkWidget *popup_window, + GdkEvent *event, + ECellCombo *ecc); +static gint e_cell_combo_button_release (GtkWidget *popup_window, + GdkEventButton *event, + ECellCombo *ecc); +static int e_cell_combo_key_press (GtkWidget *popup_window, + GdkEventKey *event, + ECellCombo *ecc); + +static void e_cell_combo_update_cell (ECellCombo *ecc); +static void e_cell_combo_restart_edit (ECellCombo *ecc); + + +static ECellPopupClass *parent_class; + + +E_MAKE_TYPE (e_cell_combo, "ECellCombo", ECellCombo, + e_cell_combo_class_init, e_cell_combo_init, + e_cell_popup_get_type()); + + +static void +e_cell_combo_class_init (GtkObjectClass *object_class) +{ + ECellPopupClass *ecpc = (ECellPopupClass *) object_class; + + object_class->destroy = e_cell_combo_destroy; + + ecpc->popup = e_cell_combo_do_popup; + + parent_class = gtk_type_class (e_cell_popup_get_type ()); +} + + +static void +e_cell_combo_init (ECellCombo *ecc) +{ + GtkWidget *frame; + + /* We create one popup window for the ECell, since there will only + ever be one popup in use at a time. */ + ecc->popup_window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_policy (GTK_WINDOW (ecc->popup_window), + TRUE, TRUE, FALSE); + + frame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (ecc->popup_window), frame); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_widget_show (frame); + + ecc->popup_scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)->hscrollbar, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)->vscrollbar, GTK_CAN_FOCUS); + gtk_container_add (GTK_CONTAINER (frame), ecc->popup_scrolled_window); + gtk_widget_show (ecc->popup_scrolled_window); + + ecc->popup_list = gtk_list_new (); + gtk_list_set_selection_mode (GTK_LIST (ecc->popup_list), + GTK_SELECTION_BROWSE); + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window), ecc->popup_list); + gtk_container_set_focus_vadjustment (GTK_CONTAINER (ecc->popup_list), + gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window))); + gtk_container_set_focus_hadjustment (GTK_CONTAINER (ecc->popup_list), + gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window))); + gtk_widget_show (ecc->popup_list); + + gtk_signal_connect (GTK_OBJECT (ecc->popup_window), + "button_press_event", + GTK_SIGNAL_FUNC (e_cell_combo_button_press), + ecc); + /* We use connect_after here so the list updates the selection before + we hide the popup and update the cell. */ + gtk_signal_connect_after (GTK_OBJECT (ecc->popup_window), + "button_release_event", + GTK_SIGNAL_FUNC (e_cell_combo_button_release), + ecc); + gtk_signal_connect (GTK_OBJECT (ecc->popup_window), + "key_press_event", + GTK_SIGNAL_FUNC (e_cell_combo_key_press), ecc); +} + + +/** + * e_cell_combo_new: + * + * Creates a new ECellCombo renderer. + * + * Returns: an ECellCombo object. + */ +ECell * +e_cell_combo_new (void) +{ + ECellCombo *ecc = gtk_type_new (e_cell_combo_get_type ()); + + return (ECell*) ecc; +} + + +/* + * GtkObject::destroy method + */ +static void +e_cell_combo_destroy (GtkObject *object) +{ + ECellCombo *ecc = E_CELL_COMBO (object); + + gtk_widget_unref (ecc->popup_window); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + + + +void +e_cell_combo_set_popdown_strings (ECellCombo *ecc, + GList *strings) +{ + GList *elem; + GtkWidget *listitem; + + g_return_if_fail (E_IS_CELL_COMBO (ecc)); + g_return_if_fail (strings != NULL); + + gtk_list_clear_items (GTK_LIST (ecc->popup_list), 0, -1); + elem = strings; + while (elem) { + listitem = gtk_list_item_new_with_label ((gchar *) elem->data); + gtk_widget_show (listitem); + gtk_container_add (GTK_CONTAINER (ecc->popup_list), listitem); + elem = elem->next; + } +} + + +static gint +e_cell_combo_do_popup (ECellPopup *ecp, + GdkEvent *event) +{ + ECellCombo *ecc = E_CELL_COMBO (ecp); + guint32 time; + gint error_code; + + e_cell_combo_show_popup (ecc); + + if (event->type == GDK_BUTTON_PRESS) { + GTK_LIST (ecc->popup_list)->drag_selection = TRUE; + time = event->button.time; + } else { + time = event->key.time; + } + + error_code = gdk_pointer_grab (ecc->popup_list->window, TRUE, + GDK_ENTER_NOTIFY_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON1_MOTION_MASK, + NULL, NULL, time); + if (error_code != 0) + g_warning ("Failed to get pointer grab (%i)", error_code); + gtk_grab_add (ecc->popup_window); + + return TRUE; +} + + +static void +e_cell_combo_show_popup (ECellCombo *ecc) +{ + gint x, y, width, height, old_width, old_height; + + g_print ("In e_cell_popup_popup_list\n"); + + /* This code is practically copied from GtkCombo. */ + old_width = ecc->popup_window->allocation.width; + old_height = ecc->popup_window->allocation.height; + + e_cell_combo_get_popup_pos (ecc, &x, &y, &height, &width); + + /* workaround for gtk_scrolled_window_size_allocate bug */ + if (old_width != width || old_height != height) { + gtk_widget_hide (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)->hscrollbar); + gtk_widget_hide (GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window)->vscrollbar); + } + + gtk_widget_set_uposition (ecc->popup_window, x, y); + gtk_widget_set_usize (ecc->popup_window, width, height); + gtk_widget_realize (ecc->popup_window); + gdk_window_resize (ecc->popup_window->window, width, height); + gtk_widget_show (ecc->popup_window); + + gtk_widget_grab_focus (ecc->popup_list); + + E_CELL_POPUP (ecc)->popup_shown = TRUE; +} + + +/* Calculates the size and position of the popup window (like GtkCombo). */ +static void +e_cell_combo_get_popup_pos (ECellCombo *ecc, + gint *x, + gint *y, + gint *height, + gint *width) +{ + ECellPopup *ecp = E_CELL_POPUP (ecc); + ETableItem *eti = E_TABLE_ITEM (ecp->popup_cell_view->cell_view.e_table_item_view); + GtkWidget *canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (eti)->canvas); + GtkBin *popwin; + GtkScrolledWindow *popup; + GtkRequisition list_requisition; + gboolean show_vscroll = FALSE, show_hscroll = FALSE; + gint avail_height, avail_width, min_height, work_height, screen_width; + gint column_width, row_height, scrollbar_width; + double x1, y1; + + /* This code is practically copied from GtkCombo. */ + popup = GTK_SCROLLED_WINDOW (ecc->popup_scrolled_window); + popwin = GTK_BIN (ecc->popup_window); + + gdk_window_get_origin (canvas->window, x, y); + + x1 = e_table_header_col_diff (eti->header, 0, eti->editing_col + 1); + y1 = e_table_item_row_diff (eti, 0, eti->editing_row + 1); + column_width = e_table_header_col_diff (eti->header, eti->editing_col, + eti->editing_col + 1); + row_height = e_table_item_row_diff (eti, eti->editing_row, + eti->editing_row + 1); + gnome_canvas_item_i2w (GNOME_CANVAS_ITEM (eti), &x1, &y1); + + *x += x1; + /* The ETable positions don't include the grid lines, I think, so we + add 1. */ + *y += y1 + 1; + + scrollbar_width = popup->vscrollbar->requisition.width + + GTK_SCROLLED_WINDOW_CLASS (GTK_OBJECT (popup)->klass)->scrollbar_spacing; + + avail_height = gdk_screen_height () - *y; + + /* We'll use the entire screen width if needed, but we save space for + the vertical scrollbar in case we need to show that. */ + screen_width = gdk_screen_width (); + avail_width = screen_width - scrollbar_width; + + gtk_widget_size_request (ecc->popup_list, &list_requisition); + min_height = MIN (list_requisition.height, + popup->vscrollbar->requisition.height); + if (!GTK_LIST (ecc->popup_list)->children) + list_requisition.height += E_CELL_COMBO_LIST_EMPTY_HEIGHT; + + /* Calculate the desired width. */ + *width = list_requisition.width + + 2 * popwin->child->style->klass->xthickness + + 2 * GTK_CONTAINER (popwin->child)->border_width + + 2 * GTK_CONTAINER (popup)->border_width + + 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width + + 2 * GTK_BIN (popup)->child->style->klass->xthickness; + + /* Use at least the same width as the column. */ + if (*width < column_width) + *width = column_width; + + /* If it is larger than the available width, use that instead and show + the horizontal scrollbar. */ + if (*width > avail_width) { + *width = avail_width; + show_hscroll = TRUE; + } + + /* Calculate all the borders etc. that we need to add to the height. */ + work_height = (2 * popwin->child->style->klass->ythickness + + 2 * GTK_CONTAINER (popwin->child)->border_width + + 2 * GTK_CONTAINER (popup)->border_width + + 2 * GTK_CONTAINER (GTK_BIN (popup)->child)->border_width + + 2 * GTK_BIN (popup)->child->style->klass->xthickness); + + /* Add on the height of the horizontal scrollbar if we need it. */ + if (show_hscroll) + work_height += popup->hscrollbar->requisition.height + + GTK_SCROLLED_WINDOW_CLASS (GTK_OBJECT (popup)->klass)->scrollbar_spacing; + + /* Check if it fits in the available height. */ + if (work_height + list_requisition.height > avail_height) { + /* It doesn't fit, so we see if we have the minimum space + needed. */ + if (work_height + min_height > avail_height + && *y - row_height > avail_height) { + /* We don't, so we show the popup above the cell + instead of below it. */ + avail_height = *y - row_height; + *y -= (work_height + list_requisition.height + + row_height); + if (*y < 0) + *y = 0; + } + } + + /* Check if we still need the vertical scrollbar. */ + if (work_height + list_requisition.height > avail_height) { + *width += scrollbar_width; + show_vscroll = TRUE; + } + + /* We try to line it up with the right edge of the column, but we don't + want it to go off the edges of the screen. */ + if (*x > screen_width) + *x = screen_width; + *x -= *width; + if (*x < 0) + *x = 0; + + if (show_vscroll) + *height = avail_height; + else + *height = work_height + list_requisition.height; +} + + + + + + +/* This handles button press events in the popup window. + Note that since we have a pointer grab on this window, we also get button + press events for windows outside the application here, so we hide the popup + window if that happens. We also get propagated events from child widgets + which we ignore. */ +static gint +e_cell_combo_button_press (GtkWidget *popup_window, + GdkEvent *event, + ECellCombo *ecc) +{ + GtkWidget *event_widget; + + g_print ("In e_cell_popup_button_press\n"); + + event_widget = gtk_get_event_widget (event); + + /* If the button press was for a widget inside the popup list, but + not the popup window itself, then we ignore the event and return + FALSE. Otherwise we will hide the popup. + Note that since we have a pointer grab on the popup list, button + presses outside the application will be reported to this window, + which is why we hide the popup in this case. */ + while (event_widget) { + event_widget = event_widget->parent; + if (event_widget == ecc->popup_list) + return FALSE; + } + + gtk_grab_remove (ecc->popup_window); + gdk_pointer_ungrab (event->button.time); + gtk_widget_hide (ecc->popup_window); + + E_CELL_POPUP (ecc)->popup_shown = FALSE; + + e_cell_combo_update_cell (ecc); + e_cell_combo_restart_edit (ecc); + + return TRUE; +} + + +/* This handles button release events in the popup window. If the button is + released inside the list, we want to hide the popup window and update the + cell with the new selection. */ +static gint +e_cell_combo_button_release (GtkWidget *popup_window, + GdkEventButton *event, + ECellCombo *ecc) +{ + GtkWidget *event_widget; + + event_widget = gtk_get_event_widget ((GdkEvent*) event); + + g_print ("In e_cell_popup_button_release event_widget:%s\n", + gtk_widget_get_name (event_widget)); + + /* See if the button was released in the list (or its children). */ + while (event_widget && event_widget != ecc->popup_list) + event_widget = event_widget->parent; + + /* If it wasn't, then we just ignore the event. */ + if (event_widget != ecc->popup_list) + return FALSE; + + /* The button was released inside the list, so we hide the popup and + update the cell to reflect the new selection. */ + gtk_grab_remove (ecc->popup_window); + gdk_pointer_ungrab (event->time); + gtk_widget_hide (ecc->popup_window); + + E_CELL_POPUP (ecc)->popup_shown = FALSE; + + e_cell_combo_update_cell (ecc); + e_cell_combo_restart_edit (ecc); + + return TRUE; +} + + +/* This handles key press events in the popup window. If the Escape key is + pressed we hide the popup, and do not change the cell contents. */ +static int +e_cell_combo_key_press (GtkWidget *popup_window, + GdkEventKey *event, + ECellCombo *ecc) +{ + g_print ("In e_cell_popup_key_press\n"); + + /* If the Escape key is pressed we hide the popup. */ + if (event->keyval != GDK_Escape + && event->keyval != GDK_Return + && event->keyval != GDK_KP_Enter + && event->keyval != GDK_ISO_Enter + && event->keyval != GDK_3270_Enter) + return FALSE; + + gtk_grab_remove (ecc->popup_window); + gdk_pointer_ungrab (event->time); + gtk_widget_hide (ecc->popup_window); + + E_CELL_POPUP (ecc)->popup_shown = FALSE; + + if (event->keyval != GDK_Escape) + e_cell_combo_update_cell (ecc); + + e_cell_combo_restart_edit (ecc); + + return TRUE; +} + + +static void +e_cell_combo_update_cell (ECellCombo *ecc) +{ + ECellPopup *ecp = E_CELL_POPUP (ecc); + ECellView *ecv = (ECellView*) ecp->popup_cell_view; + ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view); + ETableCol *ecol; + GtkList *list = GTK_LIST (ecc->popup_list); + GtkListItem *listitem; + gchar *text, *old_text; + + g_print ("In e_cell_popup_update_cell\n"); + + /* Return if no item is selected. */ + if (list->selection == NULL) + return; + + /* Get the text of the selected item. */ + listitem = list->selection->data; + gtk_label_get (GTK_LABEL (GTK_BIN (listitem)->child), &text); + + /* Compare it with the existing cell contents. */ + ecol = e_table_header_get_column (eti->header, ecp->popup_view_col); + old_text = e_table_model_value_at (ecv->e_table_model, + ecol->col_idx, ecp->popup_row); + + g_print (" Old text: %s New text: %s\n", old_text, text); + + /* If they are different, update the cell contents. */ + if (strcmp (old_text, text)) { + g_print (" Setting cell text...\n"); + e_table_model_set_value_at (ecv->e_table_model, + ecol->col_idx, ecp->popup_row, + text); + g_print (" Set cell text.\n"); + } +} + + +static void +e_cell_combo_restart_edit (ECellCombo *ecc) +{ + /* This doesn't work. ETable stops the edit straight-away again. */ +#if 0 + ECellView *ecv = (ECellView*) ecc->popup_cell_view; + ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view); + + e_table_item_enter_edit (eti, ecc->popup_view_col, ecc->popup_row); +#endif +} + + + diff --git a/widgets/table/e-cell-combo.h b/widgets/table/e-cell-combo.h new file mode 100644 index 0000000000..9f8833d029 --- /dev/null +++ b/widgets/table/e-cell-combo.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + * ECellCombo - a subclass of ECellPopup used to support popup lists like a + * GtkCombo widget. It only supports a basic popup list of strings at present, + * with no auto-completion. + */ + +#ifndef _E_CELL_COMBO_H_ +#define _E_CELL_COMBO_H_ + +#include + +#define E_CELL_COMBO_TYPE (e_cell_combo_get_type ()) +#define E_CELL_COMBO(o) (GTK_CHECK_CAST ((o), E_CELL_COMBO_TYPE, ECellCombo)) +#define E_CELL_COMBO_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_CELL_COMBO_TYPE, ECellComboClass)) +#define E_IS_CELL_COMBO(o) (GTK_CHECK_TYPE ((o), E_CELL_COMBO_TYPE)) +#define E_IS_CELL_COMBO_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_CELL_COMBO_TYPE)) + + +typedef struct { + ECellPopup parent; + + GtkWidget *popup_window; + GtkWidget *popup_scrolled_window; + GtkWidget *popup_list; +} ECellCombo; + +typedef struct { + ECellPopupClass parent_class; +} ECellComboClass; + + +GtkType e_cell_combo_get_type (void); +ECell *e_cell_combo_new (void); + +void e_cell_combo_set_popdown_strings (ECellCombo *ecc, + GList *strings); + +#endif /* _E_CELL_COMBO_H_ */ diff --git a/widgets/table/e-cell-popup.c b/widgets/table/e-cell-popup.c new file mode 100644 index 0000000000..2acc921d12 --- /dev/null +++ b/widgets/table/e-cell-popup.c @@ -0,0 +1,504 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + * ECellPopup - an abstract ECell class used to support popup selections like + * a GtkCombo widget. It contains a child ECell, e.g. an ECellText, but when + * selected it displays an arrow on the right edge which the user can click to + * show a popup. Subclasses implement the popup class function to show the + * popup. + */ + +#include +#include +#include "gal/util/e-util.h" +#include "e-table-item.h" +#include "e-cell-popup.h" + + +#define E_CELL_POPUP_ARROW_WIDTH 16 +#define E_CELL_POPUP_ARROW_XPAD 3 +#define E_CELL_POPUP_ARROW_YPAD 3 + + +static void e_cell_popup_class_init (GtkObjectClass *object_class); +static void e_cell_popup_init (ECellPopup *ecp); +static void e_cell_popup_destroy (GtkObject *object); + + +static ECellView* ecp_new_view (ECell *ecell, + ETableModel *table_model, + void *e_table_item_view); +static void ecp_kill_view (ECellView *ecv); +static void ecp_realize (ECellView *ecv); +static void ecp_unrealize (ECellView *ecv); +static void ecp_draw (ECellView *ecv, + GdkDrawable *drawable, + int model_col, + int view_col, + int row, + ECellFlags flags, + int x1, + int y1, + int x2, + int y2); +static gint ecp_event (ECellView *ecv, + GdkEvent *event, + int model_col, + int view_col, + int row, + ECellFlags flags, + ECellActions *actions); +static int ecp_height (ECellView *ecv, + int model_col, + int view_col, + int row); +static void* ecp_enter_edit (ECellView *ecv, + int model_col, + int view_col, + int row); +static void ecp_leave_edit (ECellView *ecv, + int model_col, + int view_col, + int row, + void *edit_context); +static void ecp_print (ECellView *ecv, + GnomePrintContext *context, + int model_col, + int view_col, + int row, + double width, + double height); +static gdouble ecp_print_height (ECellView *ecv, + GnomePrintContext *context, + int model_col, + int view_col, + int row, + double width); +static int ecp_max_width (ECellView *ecv, + int model_col, + int view_col); +static void ecp_show_tooltip (ECellView *ecv, + int model_col, + int view_col, + int row, + int col_width, + ETableTooltip *tooltip); + +static gint e_cell_popup_do_popup (ECellPopupView *ecp_view, + GdkEvent *event); + +static ECellClass *parent_class; + + +E_MAKE_TYPE (e_cell_popup, "ECellPopup", ECellPopup, e_cell_popup_class_init, + e_cell_popup_init, e_cell_get_type()); + + +static void +e_cell_popup_class_init (GtkObjectClass *object_class) +{ + ECellClass *ecc = (ECellClass *) object_class; + + object_class->destroy = e_cell_popup_destroy; + + ecc->new_view = ecp_new_view; + ecc->kill_view = ecp_kill_view; + ecc->realize = ecp_realize; + ecc->unrealize = ecp_unrealize; + ecc->draw = ecp_draw; + ecc->event = ecp_event; + ecc->height = ecp_height; + ecc->enter_edit = ecp_enter_edit; + ecc->leave_edit = ecp_leave_edit; + ecc->print = ecp_print; + ecc->print_height = ecp_print_height; + ecc->max_width = ecp_max_width; + ecc->show_tooltip = ecp_show_tooltip; + + parent_class = gtk_type_class (e_cell_get_type ()); +} + + +static void +e_cell_popup_init (ECellPopup *ecp) +{ + ecp->popup_shown = FALSE; +} + + +/** + * e_cell_popup_new: + * + * Creates a new ECellPopup renderer. + * + * Returns: an ECellPopup object. + */ +ECell * +e_cell_popup_new (void) +{ + ECellPopup *ecp = gtk_type_new (e_cell_popup_get_type ()); + + return (ECell*) ecp; +} + + +/* + * GtkObject::destroy method + */ +static void +e_cell_popup_destroy (GtkObject *object) +{ + ECellPopup *ecp = E_CELL_POPUP (object); + + gtk_object_unref (GTK_OBJECT (ecp->child)); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + + + +/* + * ECell::new_view method + */ +static ECellView * +ecp_new_view (ECell *ecell, ETableModel *table_model, void *e_table_item_view) +{ + ECellPopup *ecp = E_CELL_POPUP (ecell); + ECellPopupView *ecp_view; + + /* We must have a child ECell before we create any views. */ + g_return_val_if_fail (ecp->child != NULL, NULL); + + ecp_view = g_new0 (ECellPopupView, 1); + + ecp_view->cell_view.ecell = ecell; + ecp_view->cell_view.e_table_model = table_model; + ecp_view->cell_view.e_table_item_view = e_table_item_view; + + ecp_view->child_view = e_cell_new_view (ecp->child, table_model, + e_table_item_view); + + return (ECellView*) ecp_view; +} + + +/* + * ECell::kill_view method + */ +static void +ecp_kill_view (ECellView *ecv) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + if (ecp_view->child_view) + e_cell_kill_view (ecp_view->child_view); + g_free (ecp_view); +} + + +/* + * ECell::realize method + */ +static void +ecp_realize (ECellView *ecv) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + e_cell_realize (ecp_view->child_view); + + if (parent_class->realize) + (* parent_class->realize) (ecv); +} + + +/* + * ECell::unrealize method + */ +static void +ecp_unrealize (ECellView *ecv) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + e_cell_realize (ecp_view->child_view); + + if (parent_class->unrealize) + (* parent_class->unrealize) (ecv); +} + + +/* + * ECell::draw method + */ +static void +ecp_draw (ECellView *ecv, GdkDrawable *drawable, + int model_col, int view_col, int row, ECellFlags flags, + int x1, int y1, int x2, int y2) +{ + ECellPopup *ecp = E_CELL_POPUP (ecv->ecell); + ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view); + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + GtkWidget *canvas = GTK_WIDGET (GNOME_CANVAS_ITEM (ecv->e_table_item_view)->canvas); + GtkShadowType shadow; + GdkRectangle rect; + gboolean show_popup_arrow = FALSE; + + /* Display the popup arrow if we are editing this cell, or the popup + is shown for this cell. */ + if (eti->editing_col == view_col && eti->editing_row == row) { + show_popup_arrow = TRUE; + ecp->popup_arrow_shown = TRUE; + + } else if (ecp->popup_shown && ecp->popup_view_col == view_col + && ecp->popup_row == row) { + show_popup_arrow = TRUE; + } + + if (eti->editing_col == -1) + ecp->popup_arrow_shown = FALSE; + +#if 0 + g_print ("In ecp_draw row:%i col: %i %i,%i %i,%i Show Arrow:%i\n", + row, view_col, x1, y1, x2, y2, show_popup_arrow); +#endif + + if (show_popup_arrow) { + e_cell_draw (ecp_view->child_view, drawable, model_col, + view_col, row, flags, + x1, y1, x2 - E_CELL_POPUP_ARROW_WIDTH, y2); + + rect.x = x2 - E_CELL_POPUP_ARROW_WIDTH; + rect.y = y1 + 1; + rect.width = E_CELL_POPUP_ARROW_WIDTH; + rect.height = y2 - y1 - 2; + + if (ecp->popup_shown) + shadow = GTK_SHADOW_IN; + else + shadow = GTK_SHADOW_OUT; + + gtk_paint_box (canvas->style, drawable, + GTK_STATE_NORMAL, shadow, + &rect, canvas, "ecellpopup", + rect.x, rect.y, rect.width, rect.height); + gtk_paint_arrow (canvas->style, drawable, + GTK_STATE_NORMAL, shadow, + &rect, canvas, NULL, + GTK_ARROW_DOWN, TRUE, + rect.x + E_CELL_POPUP_ARROW_XPAD, + rect.y + E_CELL_POPUP_ARROW_YPAD, + rect.width - E_CELL_POPUP_ARROW_XPAD * 2, + rect.height - E_CELL_POPUP_ARROW_YPAD * 2); + } else { + e_cell_draw (ecp_view->child_view, drawable, model_col, + view_col, row, flags, x1, y1, x2, y2); + } +} + + +/* + * ECell::event method + */ +static gint +ecp_event (ECellView *ecv, GdkEvent *event, int model_col, int view_col, + int row, ECellFlags flags, ECellActions *actions) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + ECellPopup *ecp = E_CELL_POPUP (ecp_view->cell_view.ecell); + ETableItem *eti = E_TABLE_ITEM (ecv->e_table_item_view); + int width; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (eti->editing_col == view_col && eti->editing_row == row + && ecp->popup_arrow_shown) { + width = e_table_header_col_diff (eti->header, view_col, + view_col + 1); + + g_print ("Event in item popup width: %i button: %g,%g\n", + width, event->button.x, event->button.y); + + /* FIXME: The event coords seem to be relative to the + text within the cell, so we have to add 4. */ + if (event->button.x + 4 >= width - E_CELL_POPUP_ARROW_WIDTH) { + return e_cell_popup_do_popup (ecp_view, event); + } + } + break; + case GDK_BUTTON_RELEASE: + break; + case GDK_KEY_PRESS: + if (event->key.state & GDK_MOD1_MASK + && event->key.keyval == GDK_Down) { + g_print ("## Enter key pressed\n"); + return e_cell_popup_do_popup (ecp_view, event); + } + g_print ("Key Press Event ECellPopup\n"); + break; + default: + break; + } + + return e_cell_event (ecp_view->child_view, event, model_col, view_col, + row, flags, actions); +} + + +/* + * ECell::height method + */ +static int +ecp_height (ECellView *ecv, int model_col, int view_col, int row) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + return e_cell_height (ecp_view->child_view, model_col, view_col, row); +} + + +/* + * ECellView::enter_edit method + */ +static void * +ecp_enter_edit (ECellView *ecv, int model_col, int view_col, int row) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + ECellPopup *ecp = E_CELL_POPUP (ecp_view->cell_view.ecell); + + g_print ("In ecp_enter_edit model_col: %i view_col: %i row: %i\n", + model_col, view_col, row); + + if (ecp->popup_view_col != view_col || ecp->popup_row != row) + ecp->popup_arrow_shown = FALSE; + + ecp->popup_view_col = view_col; + ecp->popup_row = row; + + return e_cell_enter_edit (ecp_view->child_view, model_col, view_col, row); +} + + +/* + * ECellView::leave_edit method + */ +static void +ecp_leave_edit (ECellView *ecv, int model_col, int view_col, int row, + void *edit_context) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + g_print ("In ecp_leave_edit model_col: %i view_col: %i row: %i\n", + model_col, view_col, row); + + e_cell_leave_edit (ecp_view->child_view, model_col, view_col, row, + edit_context); +} + + +static void +ecp_print (ECellView *ecv, GnomePrintContext *context, + int model_col, int view_col, int row, double width, double height) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + e_cell_print (ecp_view->child_view, context, model_col, view_col, row, + width, height); +} + + +static gdouble +ecp_print_height (ECellView *ecv, GnomePrintContext *context, + int model_col, int view_col, int row, + double width) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + return e_cell_print_height (ecp_view->child_view, context, model_col, + view_col, row, width); +} + + +static int +ecp_max_width (ECellView *ecv, + int model_col, + int view_col) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + return e_cell_max_width (ecp_view->child_view, model_col, view_col); +} + + +static void +ecp_show_tooltip (ECellView *ecv, + int model_col, + int view_col, + int row, + int col_width, + ETableTooltip *tooltip) +{ + ECellPopupView *ecp_view = (ECellPopupView *) ecv; + + e_cell_show_tooltip (ecp_view->child_view, model_col, view_col, row, + col_width, tooltip); +} + + + +ECell* +e_cell_popup_get_child (ECellPopup *ecp) +{ + g_return_val_if_fail (E_IS_CELL_POPUP (ecp), NULL); + + return ecp->child; +} + + +void +e_cell_popup_set_child (ECellPopup *ecp, + ECell *child) +{ + g_return_if_fail (E_IS_CELL_POPUP (ecp)); + + if (ecp->child) + gtk_object_unref (GTK_OBJECT (ecp->child)); + + ecp->child = child; + gtk_object_ref (GTK_OBJECT (child)); +} + + +static gint +e_cell_popup_do_popup (ECellPopupView *ecp_view, + GdkEvent *event) +{ + ECellPopup *ecp = E_CELL_POPUP (ecp_view->cell_view.ecell); + gint (*popup_func) (ECellPopup *ecp, GdkEvent *event); + + ecp->popup_cell_view = ecp_view; + + popup_func = E_CELL_POPUP_CLASS (GTK_OBJECT (ecp)->klass)->popup; + + return popup_func ? popup_func (ecp, event) : FALSE; +} diff --git a/widgets/table/e-cell-popup.h b/widgets/table/e-cell-popup.h new file mode 100644 index 0000000000..2ccfd730ae --- /dev/null +++ b/widgets/table/e-cell-popup.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 2001, Ximian, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +/* + * ECellPopup - an ECell used to support popup selections like a GtkCombo + * widget. It contains a child ECell, e.g. an ECellText, but when selected it + * displays an arrow on the right edge which the user can click to show a + * popup. It will support subclassing or signals so that different types of + * popup can be provided. + */ + +#ifndef _E_CELL_POPUP_H_ +#define _E_CELL_POPUP_H_ + +#include +#include + +#define E_CELL_POPUP_TYPE (e_cell_popup_get_type ()) +#define E_CELL_POPUP(o) (GTK_CHECK_CAST ((o), E_CELL_POPUP_TYPE, ECellPopup)) +#define E_CELL_POPUP_CLASS(k) (GTK_CHECK_CLASS_CAST((k), E_CELL_POPUP_TYPE, ECellPopupClass)) +#define E_IS_CELL_POPUP(o) (GTK_CHECK_TYPE ((o), E_CELL_POPUP_TYPE)) +#define E_IS_CELL_POPUP_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), E_CELL_POPUP_TYPE)) + +typedef struct _ECellPopupView ECellPopupView; + +typedef struct { + ECell parent; + + ECell *child; + + /* This is TRUE if the popup window is shown for the cell being + edited. While shown we display the arrow indented. */ + gboolean popup_shown; + + /* This is TRUE if the popup arrow is shown for the cell being edited. + This is needed to stop the first click on the cell from popping up + the popup window. We only popup the window after we have drawn the + arrow. */ + gboolean popup_arrow_shown; + + /* The view in which the popup is shown. */ + ECellPopupView *popup_cell_view; + + gint popup_view_col; + gint popup_row; +} ECellPopup; + + +typedef struct { + ECellClass parent_class; + + /* Virtual function for subclasses to override. */ + gint (*popup) (ECellPopup *ecp, GdkEvent *event); +} ECellPopupClass; + + +struct _ECellPopupView { + ECellView cell_view; + + ECellView *child_view; +}; + + +GtkType e_cell_popup_get_type (void); +ECell *e_cell_popup_new (void); + +/* Get and set the child ECell. */ +ECell *e_cell_popup_get_child (ECellPopup *ecp); +void e_cell_popup_set_child (ECellPopup *ecp, + ECell *child); + +#endif /* _E_CELL_POPUP_H_ */ diff --git a/widgets/table/e-table-item.c b/widgets/table/e-table-item.c index 639d8196cf..b2e3d36372 100644 --- a/widgets/table/e-table-item.c +++ b/widgets/table/e-table-item.c @@ -613,8 +613,8 @@ eti_table_model_changed (ETableModel *table_model, ETableItem *eti) /* * Computes the distance between @start_row and @end_row in pixels */ -static int -eti_row_diff (ETableItem *eti, int start_row, int end_row) +int +e_table_item_row_diff (ETableItem *eti, int start_row, int end_row) { int row, total; @@ -646,9 +646,9 @@ eti_request_region_redraw (ETableItem *eti, if (eti->rows > 0) { x1 = e_table_header_col_diff (eti->header, 0, start_col); - y1 = eti_row_diff (eti, 0, start_row); + y1 = e_table_item_row_diff (eti, 0, start_row); width = e_table_header_col_diff (eti->header, start_col, end_col + 1); - height = eti_row_diff (eti, start_row, end_row + 1); + height = e_table_item_row_diff (eti, start_row, end_row + 1); eti_item_region_redraw (eti, eti->x1 + x1 - border, eti->y1 + y1 - border, @@ -671,9 +671,9 @@ eti_request_region_show (ETableItem *eti, int x1, y1, x2, y2; x1 = e_table_header_col_diff (eti->header, 0, start_col); - y1 = eti_row_diff (eti, 0, start_row); + y1 = e_table_item_row_diff (eti, 0, start_row); x2 = x1 + e_table_header_col_diff (eti->header, start_col, end_col + 1); - y2 = y1 + eti_row_diff (eti, start_row, end_row + 1); + y2 = y1 + e_table_item_row_diff (eti, start_row, end_row + 1); if (delay) e_canvas_item_show_area_delayed(GNOME_CANVAS_ITEM(eti), x1, y1, x2, y2, delay); @@ -1451,7 +1451,7 @@ find_cell (ETableItem *eti, double x, double y, int *col_res, int *row_res, doub *col_res = eti->grabbed_col; *row_res = eti->grabbed_row; *x1_res = x - eti->x1 - e_table_header_col_diff (eti->header, 0, eti->grabbed_col); - *y1_res = y - eti->y1 - eti_row_diff (eti, 0, eti->grabbed_row); + *y1_res = y - eti->y1 - e_table_item_row_diff (eti, 0, eti->grabbed_row); return TRUE; } diff --git a/widgets/table/e-table-item.h b/widgets/table/e-table-item.h index 50a5849313..7d6c6919e6 100644 --- a/widgets/table/e-table-item.h +++ b/widgets/table/e-table-item.h @@ -140,6 +140,9 @@ void e_table_item_compute_location (ETableItem *eti, int *row, int *col); +int e_table_item_row_diff (ETableItem *eti, + int start_row, + int end_row); END_GNOME_DECLS -- cgit v1.2.3