diff options
-rw-r--r-- | widgets/misc/.cvsignore | 1 | ||||
-rw-r--r-- | widgets/misc/ChangeLog | 15 | ||||
-rw-r--r-- | widgets/misc/Makefile.am | 14 | ||||
-rw-r--r-- | widgets/misc/e-calendar-item.c | 44 | ||||
-rw-r--r-- | widgets/misc/e-calendar-item.h | 11 | ||||
-rw-r--r-- | widgets/misc/e-dateedit.c | 957 | ||||
-rw-r--r-- | widgets/misc/e-dateedit.h | 110 | ||||
-rw-r--r-- | widgets/misc/test-dateedit.c | 114 |
8 files changed, 1258 insertions, 8 deletions
diff --git a/widgets/misc/.cvsignore b/widgets/misc/.cvsignore index 411f0fc219..0d7b5ae81b 100644 --- a/widgets/misc/.cvsignore +++ b/widgets/misc/.cvsignore @@ -7,3 +7,4 @@ Makefile.in *.la test-title-bar test-calendar +test-dateedit diff --git a/widgets/misc/ChangeLog b/widgets/misc/ChangeLog index e3bff75c47..a3619747a6 100644 --- a/widgets/misc/ChangeLog +++ b/widgets/misc/ChangeLog @@ -1,3 +1,18 @@ +2000-09-11 Damon Chaplin <damon@helixcode.com> + + * Makefile.am (libemiscwidgets_a_SOURCES): added e-dateedit.[hc] + and the test-dateedit app. + + * e-dateedit.[hc]: new widget to use instead of GnomeDateEdit. It + uses the new ECalendar widget for the calendar and also supports + "None", "Today" and "Now" buttons, and goes away with a single click. + + * test-dateedit.c: app to test the EDateEdit widget. + + * e-calendar-item.c: updated to support the EDateEdit better, + mainly by adding the "move_selection_when_moving" arg so we can turn + it off to keep the same day selected when changing the months shown. + 2000-09-05 Damon Chaplin <damon@helixcode.com> * e-calendar-item.c (e_calendar_item_draw_month): make sure we get diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am index e2872e7006..1fd1008c9e 100644 --- a/widgets/misc/Makefile.am +++ b/widgets/misc/Makefile.am @@ -17,6 +17,8 @@ libemiscwidgets_a_SOURCES = \ e-calendar-item.h \ e-clipped-label.c \ e-clipped-label.h \ + e-dateedit.c \ + e-dateedit.h \ e-scroll-frame.c \ e-scroll-frame.h \ e-title-bar.c \ @@ -24,7 +26,8 @@ libemiscwidgets_a_SOURCES = \ noinst_PROGRAMS = \ test-title-bar \ - test-calendar + test-calendar \ + test-dateedit test_title_bar_SOURCES = \ test-title-bar.c @@ -40,3 +43,12 @@ test_calendar_LDADD = \ ./libemiscwidgets.a \ ../../e-util/libeutil.la \ $(EXTRA_GNOME_LIBS) + +test_dateedit_SOURCES = \ + test-dateedit.c + +test_dateedit_LDADD = \ + ./libemiscwidgets.a \ + ../../e-util/libeutil.la \ + $(EXTRA_GNOME_LIBS) + diff --git a/widgets/misc/e-calendar-item.c b/widgets/misc/e-calendar-item.c index 53f2608347..4c82f1c5f2 100644 --- a/widgets/misc/e-calendar-item.c +++ b/widgets/misc/e-calendar-item.c @@ -241,6 +241,7 @@ enum { ARG_SHOW_WEEK_NUMBERS, ARG_MAXIMUM_DAYS_SELECTED, ARG_DAYS_TO_START_WEEK_SELECTION, + ARG_MOVE_SELECTION_WHEN_MOVING, ARG_ROUND_SELECTION_WHEN_MOVING }; @@ -328,6 +329,9 @@ e_calendar_item_class_init (ECalendarItemClass *class) gtk_object_add_arg_type ("ECalendarItem::days_to_start_week_selection", GTK_TYPE_INT, GTK_ARG_READWRITE, ARG_DAYS_TO_START_WEEK_SELECTION); + gtk_object_add_arg_type ("ECalendarItem::move_selection_when_moving", + GTK_TYPE_BOOL, GTK_ARG_READWRITE, + ARG_MOVE_SELECTION_WHEN_MOVING); gtk_object_add_arg_type ("ECalendarItem::round_selection_when_moving", GTK_TYPE_BOOL, GTK_ARG_READWRITE, ARG_ROUND_SELECTION_WHEN_MOVING); @@ -395,6 +399,7 @@ e_calendar_item_init (ECalendarItem *calitem) calitem->expand = TRUE; calitem->max_days_selected = 42; calitem->days_to_start_week_selection = 9; + calitem->move_selection_when_moving = TRUE; calitem->round_selection_when_moving = FALSE; calitem->x1 = 0.0; @@ -404,7 +409,7 @@ e_calendar_item_init (ECalendarItem *calitem) calitem->buttons_space = 0.0; - calitem->selection_start_month_offset = -2; + calitem->selection_set = FALSE; calitem->selection_changed = FALSE; calitem->date_range_changed = FALSE; @@ -512,6 +517,9 @@ e_calendar_item_get_arg (GtkObject *o, GtkArg *arg, guint arg_id) case ARG_DAYS_TO_START_WEEK_SELECTION: GTK_VALUE_INT (*arg) = calitem->days_to_start_week_selection; break; + case ARG_MOVE_SELECTION_WHEN_MOVING: + GTK_VALUE_BOOL (*arg) = calitem->move_selection_when_moving; + break; case ARG_ROUND_SELECTION_WHEN_MOVING: GTK_VALUE_BOOL (*arg) = calitem->round_selection_when_moving; break; @@ -654,6 +662,10 @@ e_calendar_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) ivalue = GTK_VALUE_INT (*arg); calitem->days_to_start_week_selection = ivalue; break; + case ARG_MOVE_SELECTION_WHEN_MOVING: + bvalue = GTK_VALUE_BOOL (*arg); + calitem->move_selection_when_moving = bvalue; + break; case ARG_ROUND_SELECTION_WHEN_MOVING: bvalue = GTK_VALUE_BOOL (*arg); calitem->round_selection_when_moving = bvalue; @@ -1258,7 +1270,7 @@ e_calendar_item_draw_day_numbers (ECalendarItem *calitem, && months[mon] == today_month && day_num == today_mday; - selected = calitem->selection_start_month_offset != -2 + selected = calitem->selection_set && (calitem->selection_start_month_offset < month_offset || (calitem->selection_start_month_offset == month_offset && calitem->selection_start_day <= day_num)) @@ -1631,6 +1643,7 @@ e_calendar_item_button_press (ECalendarItem *calitem, NULL, event->button.time) != 0) return FALSE; + calitem->selection_set = TRUE; calitem->selection_start_month_offset = month_offset; calitem->selection_start_day = day; calitem->selection_end_month_offset = month_offset; @@ -2077,9 +2090,10 @@ e_calendar_item_set_first_month(ECalendarItem *calitem, months_diff = (new_year - calitem->year) * 12 + new_month - calitem->month; - if (calitem->selection_start_month_offset != -2) { - if (calitem->selection_start_month_offset - months_diff >= 0 - && calitem->selection_end_month_offset - months_diff < num_months) { + if (calitem->selection_set) { + if (!calitem->move_selection_when_moving + || (calitem->selection_start_month_offset - months_diff >= 0 + && calitem->selection_end_month_offset - months_diff < num_months)) { calitem->selection_start_month_offset -= months_diff; calitem->selection_end_month_offset -= months_diff; calitem->selection_real_start_month_offset -= months_diff; @@ -2441,7 +2455,7 @@ e_calendar_item_get_selection (ECalendarItem *calitem, g_date_clear (start_date, 1); g_date_clear (end_date, 1); - if (calitem->selection_start_month_offset == -2) + if (!calitem->selection_set) return FALSE; start_year = calitem->year; @@ -2473,6 +2487,20 @@ e_calendar_item_set_selection (ECalendarItem *calitem, gboolean need_update; g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + /* If start_date is NULL, we clear the selection without changing the + month shown. */ + if (start_date == NULL) { + calitem->selection_set = FALSE; + calitem->selection_changed = TRUE; + e_calendar_item_queue_signal_emission (calitem); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); + return; + } + + if (end_date == NULL) + end_date = start_date; + g_return_if_fail (g_date_compare (start_date, end_date) <= 0); start_year = g_date_year (start_date); @@ -2500,13 +2528,15 @@ e_calendar_item_set_selection (ECalendarItem *calitem, new_end_day = end_day; - if (calitem->selection_start_month_offset != new_start_month_offset + if (!calitem->selection_set + || calitem->selection_start_month_offset != new_start_month_offset || calitem->selection_start_day != new_start_day || calitem->selection_end_month_offset != new_end_month_offset || calitem->selection_end_day != new_end_day) { need_update = TRUE; calitem->selection_changed = TRUE; e_calendar_item_queue_signal_emission (calitem); + calitem->selection_set = TRUE; calitem->selection_start_month_offset = new_start_month_offset; calitem->selection_start_day = new_start_day; calitem->selection_end_month_offset = new_end_month_offset; diff --git a/widgets/misc/e-calendar-item.h b/widgets/misc/e-calendar-item.h index 3091e47bb4..3820b84131 100644 --- a/widgets/misc/e-calendar-item.h +++ b/widgets/misc/e-calendar-item.h @@ -126,6 +126,11 @@ struct _ECalendarItem weeks, or -1 if we never switch. Defaults to -1. */ gint days_to_start_week_selection; + /* Whether the selection is moved when we move back/forward one month. + Used for things like the EDateEdit which only want the selection to + be changed when the user explicitly selects a day. */ + gboolean move_selection_when_moving; + /* Whether the selection is rounded down to the nearest week when we move back/forward one month. Used for the week view. */ gboolean round_selection_when_moving; @@ -165,6 +170,7 @@ struct _ECalendarItem gboolean selecting; gboolean selection_dragging_end; gboolean selection_from_full_week; + gboolean selection_set; gint selection_start_month_offset; gint selection_start_day; gint selection_end_month_offset; @@ -236,9 +242,14 @@ void e_calendar_item_get_date_range (ECalendarItem *calitem, gint *end_month, gint *end_day); +/* Returns the selected date range. It returns FALSE if no days are currently + selected. */ gboolean e_calendar_item_get_selection (ECalendarItem *calitem, GDate *start_date, GDate *end_date); +/* Sets the selected date range, and changes the date range shown so at least + the start of the selection is shown. If start_date is NULL it clears the + selection. */ void e_calendar_item_set_selection (ECalendarItem *calitem, GDate *start_date, GDate *end_date); diff --git a/widgets/misc/e-dateedit.c b/widgets/misc/e-dateedit.c new file mode 100644 index 0000000000..dac3cce0c6 --- /dev/null +++ b/widgets/misc/e-dateedit.c @@ -0,0 +1,957 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * Copyright 2000, Helix Code, Inc. + * + * Based on the GnomeDateEdit, part of the Gnome Library. + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * + * 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 + */ + +/* + * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional + * time field with popups for entering a date. + */ + +/* We need this for strptime. */ +#define _XOPEN_SOURCE + +#include <config.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <gtk/gtkmain.h> +#include "e-dateedit.h" +#include "e-calendar.h" + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkarrow.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkwindow.h> + +struct _EDateEditPrivate { + GtkWidget *date_entry; + GtkWidget *date_button; + + GtkWidget *space; + + GtkWidget *time_combo; + + GtkWidget *cal_popup; + GtkWidget *calendar; + GtkWidget *now_button; + GtkWidget *today_button; + GtkWidget *none_button; + + gboolean show_time; + gboolean use_24_hour_format; + + gint lower_hour; + gint upper_hour; +}; + +enum { + DATE_CHANGED, + TIME_CHANGED, + LAST_SIGNAL +}; + + +static gint date_edit_signals [LAST_SIGNAL] = { 0 }; + + +static void e_date_edit_class_init (EDateEditClass *class); +static void e_date_edit_init (EDateEdit *dedit); +static void create_children (EDateEdit *dedit); +static void e_date_edit_destroy (GtkObject *object); +static void e_date_edit_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); + +static void on_date_button_clicked (GtkWidget *widget, + EDateEdit *dedit); +static void position_date_popup (EDateEdit *dedit); +static void on_date_popup_none_button_clicked (GtkWidget *button, + EDateEdit *dedit); +static void on_date_popup_today_button_clicked (GtkWidget *button, + EDateEdit *dedit); +static void on_date_popup_now_button_clicked (GtkWidget *button, + EDateEdit *dedit); +static gint on_date_popup_delete_event (GtkWidget *widget, + gpointer data); +static gint on_date_popup_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer data); +static gint on_date_popup_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer data); +static void on_date_popup_date_selected (ECalendarItem *calitem, + EDateEdit *dedit); +static void hide_date_popup (EDateEdit *dedit); +static void rebuild_time_popup (EDateEdit *dedit); +static void enable_time_combo (EDateEdit *dedit); +static void disable_time_combo (EDateEdit *dedit); +static gboolean date_is_none (char *date_text); + + +static GtkHBoxClass *parent_class; + +/** + * e_date_edit_get_type: + * + * Returns the GtkType for the EDateEdit widget + */ +guint +e_date_edit_get_type (void) +{ + static guint date_edit_type = 0; + + if (!date_edit_type){ + GtkTypeInfo date_edit_info = { + "EDateEdit", + sizeof (EDateEdit), + sizeof (EDateEditClass), + (GtkClassInitFunc) e_date_edit_class_init, + (GtkObjectInitFunc) e_date_edit_init, + NULL, + NULL, + }; + + date_edit_type = gtk_type_unique (gtk_hbox_get_type (), &date_edit_info); + } + + return date_edit_type; +} + + +static void +e_date_edit_class_init (EDateEditClass *class) +{ + GtkObjectClass *object_class = (GtkObjectClass *) class; + GtkContainerClass *container_class = (GtkContainerClass *) class; + + object_class = (GtkObjectClass*) class; + + parent_class = gtk_type_class (gtk_hbox_get_type ()); + + date_edit_signals [TIME_CHANGED] = + gtk_signal_new ("time_changed", + GTK_RUN_FIRST, object_class->type, + GTK_SIGNAL_OFFSET (EDateEditClass, + time_changed), + gtk_signal_default_marshaller, + GTK_TYPE_NONE, 0); + + date_edit_signals [DATE_CHANGED] = + gtk_signal_new ("date_changed", + GTK_RUN_FIRST, object_class->type, + GTK_SIGNAL_OFFSET (EDateEditClass, + date_changed), + gtk_signal_default_marshaller, + GTK_TYPE_NONE, 0); + + gtk_object_class_add_signals (object_class, date_edit_signals, + LAST_SIGNAL); + + container_class->forall = e_date_edit_forall; + + object_class->destroy = e_date_edit_destroy; + + class->date_changed = NULL; + class->time_changed = NULL; +} + + +static void +e_date_edit_init (EDateEdit *dedit) +{ + EDateEditPrivate *priv; + + dedit->_priv = priv = g_new0 (EDateEditPrivate, 1); + + priv->show_time = TRUE; + priv->use_24_hour_format = TRUE; + + priv->lower_hour = 0; + priv->upper_hour = 24; +} + + +/** + * e_date_edit_new: + * + * Description: Creates a new #EDateEdit widget which can be used + * to provide an easy to use way for entering dates and times. + * + * Returns: a new #EDateEdit widget. + */ +GtkWidget * +e_date_edit_new (void) +{ + EDateEdit *dedit; + + dedit = gtk_type_new (e_date_edit_get_type ()); + + create_children (dedit); + e_date_edit_set_time (dedit, 0); + + return GTK_WIDGET (dedit); +} + + +static void +create_children (EDateEdit *dedit) +{ + EDateEditPrivate *priv; + ECalendar *calendar; + GtkWidget *frame, *arrow; + GtkWidget *vbox, *bbox; + + priv = dedit->_priv; + + priv->date_entry = gtk_entry_new (); + gtk_widget_set_usize (priv->date_entry, 90, 0); + gtk_box_pack_start (GTK_BOX (dedit), priv->date_entry, FALSE, TRUE, 0); + gtk_widget_show (priv->date_entry); + + priv->date_button = gtk_button_new (); + gtk_signal_connect (GTK_OBJECT (priv->date_button), "clicked", + GTK_SIGNAL_FUNC (on_date_button_clicked), dedit); + gtk_box_pack_start (GTK_BOX (dedit), priv->date_button, + FALSE, FALSE, 0); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (priv->date_button), arrow); + gtk_widget_show (arrow); + + gtk_widget_show (priv->date_button); + + /* This is just to create a space between the date & time parts. */ + priv->space = gtk_drawing_area_new (); + gtk_box_pack_start (GTK_BOX (dedit), priv->space, FALSE, FALSE, 2); + + + priv->time_combo = gtk_combo_new (); + gtk_widget_set_usize (GTK_COMBO (priv->time_combo)->entry, 90, 0); + gtk_box_pack_start (GTK_BOX (dedit), priv->time_combo, FALSE, TRUE, 0); + rebuild_time_popup (dedit); + + if (priv->show_time) { + gtk_widget_show (priv->space); + gtk_widget_show (priv->time_combo); + } + + priv->cal_popup = gtk_window_new (GTK_WINDOW_POPUP); + gtk_widget_set_events (priv->cal_popup, + gtk_widget_get_events (priv->cal_popup) + | GDK_KEY_PRESS_MASK); + gtk_signal_connect (GTK_OBJECT (priv->cal_popup), "delete_event", + (GtkSignalFunc) on_date_popup_delete_event, + dedit); + gtk_signal_connect (GTK_OBJECT (priv->cal_popup), "key_press_event", + (GtkSignalFunc) on_date_popup_key_press, + dedit); + gtk_signal_connect (GTK_OBJECT (priv->cal_popup), "button_press_event", + (GtkSignalFunc) on_date_popup_button_press, + dedit); + gtk_window_set_policy (GTK_WINDOW (priv->cal_popup), + FALSE, FALSE, TRUE); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (priv->cal_popup), frame); + gtk_widget_show (frame); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (frame), vbox); + gtk_widget_show (vbox); + + priv->calendar = e_calendar_new (); + calendar = E_CALENDAR (priv->calendar); + /*e_calendar_set_buttons (calendar, TRUE, TRUE);*/ + gnome_canvas_item_set (GNOME_CANVAS_ITEM (calendar->calitem), + "maximum_days_selected", 1, + "move_selection_when_moving", FALSE, + NULL); + + gtk_signal_connect (GTK_OBJECT (calendar->calitem), + "selection_changed", + GTK_SIGNAL_FUNC (on_date_popup_date_selected), dedit); + + gtk_box_pack_start (GTK_BOX (vbox), priv->calendar, FALSE, FALSE, 0); + gtk_widget_show (priv->calendar); + + bbox = gtk_hbutton_box_new (); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 4); + gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), 2); + gtk_button_box_set_child_ipadding (GTK_BUTTON_BOX (bbox), 2, 0); + gtk_button_box_set_child_size (GTK_BUTTON_BOX (bbox), 0, 0); + gtk_box_pack_start (GTK_BOX (vbox), bbox, FALSE, FALSE, 0); + gtk_widget_show (bbox); + + priv->now_button = gtk_button_new_with_label (_("Now")); + gtk_container_add (GTK_CONTAINER (bbox), priv->now_button); + gtk_widget_show (priv->now_button); + gtk_signal_connect (GTK_OBJECT (priv->now_button), "clicked", + GTK_SIGNAL_FUNC (on_date_popup_now_button_clicked), dedit); + + priv->today_button = gtk_button_new_with_label (_("Today")); + gtk_container_add (GTK_CONTAINER (bbox), priv->today_button); + gtk_widget_show (priv->today_button); + gtk_signal_connect (GTK_OBJECT (priv->today_button), "clicked", + GTK_SIGNAL_FUNC (on_date_popup_today_button_clicked), dedit); + + priv->none_button = gtk_button_new_with_label (_("None")); + gtk_container_add (GTK_CONTAINER (bbox), priv->none_button); + gtk_signal_connect (GTK_OBJECT (priv->none_button), "clicked", + GTK_SIGNAL_FUNC (on_date_popup_none_button_clicked), dedit); +} + + +static void +e_date_edit_destroy (GtkObject *object) +{ + EDateEdit *dedit; + + g_return_if_fail (object != NULL); + g_return_if_fail (E_IS_DATE_EDIT (object)); + + dedit = E_DATE_EDIT (object); + + gtk_widget_destroy (dedit->_priv->cal_popup); + gtk_widget_unref (dedit->_priv->cal_popup); + + g_free(dedit->_priv); + dedit->_priv = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + + +static void +e_date_edit_forall (GtkContainer *container, gboolean include_internals, + GtkCallback callback, gpointer callback_data) +{ + g_return_if_fail (container != NULL); + g_return_if_fail (E_IS_DATE_EDIT (container)); + g_return_if_fail (callback != NULL); + + /* Let GtkBox handle things only if the internal widgets need to be + * poked. + */ + if (include_internals) + if (GTK_CONTAINER_CLASS (parent_class)->forall) + (* GTK_CONTAINER_CLASS (parent_class)->forall) + (container, + include_internals, + callback, + callback_data); +} + + +/* The arrow button beside the date field has been clicked, so we show the + popup with the ECalendar in. */ +static void +on_date_button_clicked (GtkWidget *widget, EDateEdit *dedit) +{ + EDateEditPrivate *priv; + ECalendar *calendar; + struct tm mtm; + gchar *date_text, *status; + GDate selected_day; + gboolean clear_selection = FALSE; + + priv = dedit->_priv; + calendar = E_CALENDAR (priv->calendar); + + date_text = gtk_entry_get_text (GTK_ENTRY (dedit->_priv->date_entry)); + if (date_is_none (date_text)) { + clear_selection = TRUE; + } else { + status = strptime (date_text, "%x", &mtm); + if (!status || !status[0]) + clear_selection = TRUE; + } + + if (clear_selection) { + e_calendar_item_set_selection (calendar->calitem, NULL, NULL); + } else { + g_date_clear (&selected_day, 1); + g_date_set_dmy (&selected_day, mtm.tm_mday, mtm.tm_mon + 1, + mtm.tm_year + 1900); + e_calendar_item_set_selection (calendar->calitem, + &selected_day, NULL); + } + + /* FIXME: Hack. Change ECalendarItem so it doesn't queue signal + emissions. */ + calendar->calitem->selection_changed = FALSE; + + position_date_popup (dedit); + + gtk_widget_realize (dedit->_priv->cal_popup); + gtk_widget_show (dedit->_priv->cal_popup); + + gtk_widget_grab_focus (dedit->_priv->cal_popup); + + gtk_grab_add (dedit->_priv->cal_popup); + + gdk_pointer_grab (dedit->_priv->cal_popup->window, TRUE, + (GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK), + NULL, NULL, GDK_CURRENT_TIME); +} + + +/* This positions the date popup below and to the left of the arrow button, + just before it is shown. */ +static void +position_date_popup (EDateEdit *dedit) +{ + gint x, y; + gint bwidth, bheight; + GtkRequisition req; + gint screen_width, screen_height; + + gtk_widget_size_request (dedit->_priv->cal_popup, &req); + + gdk_window_get_origin (dedit->_priv->date_button->window, &x, &y); + gdk_window_get_size (dedit->_priv->date_button->window, + &bwidth, &bheight); + + screen_width = gdk_screen_width (); + screen_height = gdk_screen_height (); + + x += bwidth - req.width; + y += bheight; + + x = CLAMP (x, 0, MAX (0, screen_width - req.width)); + y = CLAMP (y, 0, MAX (0, screen_height - req.height)); + + gtk_widget_set_uposition (dedit->_priv->cal_popup, x, y); +} + + +/* A date has been selected in the date popup, so we set the date field + and hide the popup. */ +static void +on_date_popup_date_selected (ECalendarItem *calitem, EDateEdit *dedit) +{ + GDate start_date, end_date; + struct tm tmp_tm; + char buffer [40]; + + if (!e_calendar_item_get_selection (calitem, &start_date, &end_date)) + return; + + g_date_to_struct_tm (&start_date, &tmp_tm); + + strftime (buffer, sizeof (buffer), "%x", &tmp_tm); + gtk_entry_set_text (GTK_ENTRY (dedit->_priv->date_entry), buffer); + + enable_time_combo (dedit); + + gtk_signal_emit (GTK_OBJECT (dedit), date_edit_signals [DATE_CHANGED]); + hide_date_popup (dedit); +} + + +static void +on_date_popup_now_button_clicked (GtkWidget *button, + EDateEdit *dedit) +{ + e_date_edit_set_time (dedit, time (NULL)); + gtk_signal_emit (GTK_OBJECT (dedit), date_edit_signals [DATE_CHANGED]); + hide_date_popup (dedit); +} + + +static void +on_date_popup_today_button_clicked (GtkWidget *button, + EDateEdit *dedit) +{ + struct tm *tmp_tm; + time_t t; + char buffer [40]; + + t = time (NULL); + tmp_tm = localtime (&t); + strftime (buffer, sizeof (buffer), "%x", tmp_tm); + gtk_entry_set_text (GTK_ENTRY (dedit->_priv->date_entry), buffer); + + enable_time_combo (dedit); + + gtk_signal_emit (GTK_OBJECT (dedit), date_edit_signals [DATE_CHANGED]); + hide_date_popup (dedit); +} + + +static void +on_date_popup_none_button_clicked (GtkWidget *button, + EDateEdit *dedit) +{ + e_date_edit_set_time (dedit, -1); + gtk_signal_emit (GTK_OBJECT (dedit), date_edit_signals [DATE_CHANGED]); + hide_date_popup (dedit); +} + + +/* A key has been pressed while the date popup is showing. If it is the Escape + key we hide the popup. */ +static gint +on_date_popup_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + EDateEdit *dedit; + + if (event->keyval != GDK_Escape) + return FALSE; + + dedit = data; + gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event"); + hide_date_popup (dedit); + + return TRUE; +} + + +/* A mouse button has been pressed while the date popup is showing. + Any button press events used to select days etc. in the popup will have + have been handled elsewhere, so here we just hide the popup. + (This function is yanked from gtkcombo.c) */ +static gint +on_date_popup_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + EDateEdit *dedit; + GtkWidget *child; + + dedit = data; + + child = gtk_get_event_widget ((GdkEvent *) event); + + /* We don't ask for button press events on the grab widget, so + * if an event is reported directly to the grab widget, it must + * be on a window outside the application (and thus we remove + * the popup window). Otherwise, we check if the widget is a child + * of the grab widget, and only remove the popup window if it + * is not. + */ + if (child != widget) { + while (child) { + if (child == widget) + return FALSE; + child = child->parent; + } + } + + hide_date_popup (dedit); + + return TRUE; +} + + +/* A delete event has been received for the date popup, so we hide it and + return TRUE so it doesn't get destroyed. */ +static gint +on_date_popup_delete_event (GtkWidget *widget, gpointer data) +{ + EDateEdit *dedit; + + dedit = data; + hide_date_popup (dedit); + + return TRUE; +} + + +/* Hides the date popup, removing any grabs. */ +static void +hide_date_popup (EDateEdit *dedit) +{ + gtk_widget_hide (dedit->_priv->cal_popup); + gtk_grab_remove (dedit->_priv->cal_popup); + gdk_pointer_ungrab (GDK_CURRENT_TIME); +} + + +/** + * e_date_edit_get_time: + * @dedit: The EDateEdit widget + * + * Returns the time entered in the EDateEdit widget + */ +time_t +e_date_edit_get_time (EDateEdit *dedit) +{ + EDateEditPrivate *priv; + struct tm date_tm = { 0 }, time_tm = { 0 }; + char *date_text, *time_text, *format; + + g_return_val_if_fail (dedit != NULL, -1); + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), -1); + + priv = dedit->_priv; + + date_text = gtk_entry_get_text (GTK_ENTRY (priv->date_entry)); + if (date_is_none (date_text)) + return -1; + + strptime (date_text, "%x", &date_tm); + + if (dedit->_priv->show_time) { + time_text = gtk_entry_get_text (GTK_ENTRY (GTK_COMBO (priv->time_combo)->entry)); + + if (priv->use_24_hour_format) + format = "%H:%M"; + else + format = "%I:%M %p"; + + strptime (time_text, format, &time_tm); + + date_tm.tm_hour = time_tm.tm_hour; + date_tm.tm_min = time_tm.tm_min; + } + + date_tm.tm_isdst = -1; + + return mktime (&date_tm); +} + + +/** + * e_date_edit_set_time: + * @dedit: the EDateEdit widget + * @the_time: The time and date that should be set on the widget + * + * Description: Changes the displayed date and time in the EDateEdit + * widget to be the one represented by @the_time. If @the_time is 0 + * then current time is used. + */ +void +e_date_edit_set_time (EDateEdit *dedit, time_t the_time) +{ + EDateEditPrivate *priv; + struct tm *mytm; + char buffer[40], *format; + + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + priv = dedit->_priv; + + if (the_time == -1) { + gtk_entry_set_text (GTK_ENTRY (priv->date_entry), _("None")); + disable_time_combo (dedit); + return; + } + + enable_time_combo (dedit); + + if (the_time == 0) + the_time = time (NULL); + + mytm = localtime (&the_time); + + /* Set the date */ + strftime (buffer, sizeof (buffer), "%x", mytm); + gtk_entry_set_text (GTK_ENTRY (priv->date_entry), buffer); + + /* Set the time */ + if (priv->use_24_hour_format) + format = "%H:%M"; + else + format = "%I:%M %p"; + + strftime (buffer, sizeof (buffer), format, mytm); + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (priv->time_combo)->entry), + buffer); +} + + +/* Whether we show the time field. */ +gboolean +e_date_edit_get_show_time (EDateEdit *dedit) +{ + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); + + return dedit->_priv->show_time; +} + + +void +e_date_edit_set_show_time (EDateEdit *dedit, + gboolean show_time) +{ + EDateEditPrivate *priv; + + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + priv = dedit->_priv; + + if (priv->show_time == show_time) + return; + + priv->show_time = show_time; + + if (show_time) { + gtk_widget_show (priv->space); + gtk_widget_show (priv->time_combo); + } else { + gtk_widget_hide (priv->space); + gtk_widget_hide (priv->time_combo); + gtk_widget_hide (priv->now_button); + } +} + + +/* The week start day, used in the date popup. 0 (Sun) to 6 (Sat). */ +gint +e_date_edit_get_week_start_day (EDateEdit *dedit) +{ + gint week_start_day; + + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), 1); + + gtk_object_get (GTK_OBJECT (E_CALENDAR (dedit->_priv->calendar)->calitem), + "week_start_day", &week_start_day, + NULL); + + return week_start_day; +} + + +void +e_date_edit_set_week_start_day (EDateEdit *dedit, + gint week_start_day) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + gnome_canvas_item_set (GNOME_CANVAS_ITEM (E_CALENDAR (dedit->_priv->calendar)->calitem), + "week_start_day", week_start_day, + NULL); +} + + +/* Whether we show week numbers in the date popup. */ +gboolean +e_date_edit_get_show_week_numbers (EDateEdit *dedit) +{ + gboolean show_week_numbers; + + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); + + gtk_object_get (GTK_OBJECT (E_CALENDAR (dedit->_priv->calendar)->calitem), + "show_week_numbers", &show_week_numbers, + NULL); + + return show_week_numbers; +} + + +void +e_date_edit_set_show_week_numbers (EDateEdit *dedit, + gboolean show_week_numbers) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + gnome_canvas_item_set (GNOME_CANVAS_ITEM (E_CALENDAR (dedit->_priv->calendar)->calitem), + "show_week_numbers", show_week_numbers, + NULL); +} + + +/* Whether we use 24 hour format in the time field & popup. */ +gboolean +e_date_edit_get_use_24_hour_format (EDateEdit *dedit) +{ + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), TRUE); + + return dedit->_priv->use_24_hour_format; +} + + +void +e_date_edit_set_use_24_hour_format (EDateEdit *dedit, + gboolean use_24_hour_format) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + if (dedit->_priv->use_24_hour_format == use_24_hour_format) + return; + + dedit->_priv->use_24_hour_format = use_24_hour_format; + + rebuild_time_popup (dedit); +} + + +/* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will + return (time_t) -1 in this case. */ +gboolean +e_date_edit_get_allow_no_date_set (EDateEdit *dedit) +{ + g_return_val_if_fail (E_IS_DATE_EDIT (dedit), FALSE); + + return GTK_WIDGET_VISIBLE (dedit->_priv->none_button); +} + + +void +e_date_edit_set_allow_no_date_set (EDateEdit *dedit, + gboolean allow_no_date_set) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + if (allow_no_date_set) { + gtk_widget_show (dedit->_priv->none_button); + } else { + gtk_widget_hide (dedit->_priv->none_button); + + /* If currently set to 'None' set to the current time. */ + if (e_date_edit_get_time (dedit) == -1) + e_date_edit_set_time (dedit, time (NULL)); + } +} + + +/* The range of time to show in the time combo popup. */ +void +e_date_edit_get_time_popup_range (EDateEdit *dedit, + gint *lower_hour, + gint *upper_hour) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + *lower_hour = dedit->_priv->lower_hour; + *upper_hour = dedit->_priv->upper_hour; +} + + +void +e_date_edit_set_time_popup_range (EDateEdit *dedit, + gint lower_hour, + gint upper_hour) +{ + g_return_if_fail (E_IS_DATE_EDIT (dedit)); + + if (dedit->_priv->lower_hour == lower_hour + && dedit->_priv->upper_hour == upper_hour) + return; + + dedit->_priv->lower_hour = lower_hour; + dedit->_priv->upper_hour = upper_hour; + + rebuild_time_popup (dedit); +} + + +static void +rebuild_time_popup (EDateEdit *dedit) +{ + EDateEditPrivate *priv; + GtkList *list; + GtkWidget *listitem; + char buffer[40], *format; + struct tm tmp_tm; + gint hour, min; + + priv = dedit->_priv; + + list = GTK_LIST (GTK_COMBO (priv->time_combo)->list); + + gtk_list_clear_items (list, 0, -1); + + /* Fill the struct tm with some sane values. */ + tmp_tm.tm_year = 2000; + tmp_tm.tm_mon = 0; + tmp_tm.tm_mday = 1; + tmp_tm.tm_sec = 0; + tmp_tm.tm_isdst = 0; + + for (hour = priv->lower_hour; hour <= priv->upper_hour; hour++) { + + /* We don't want to display midnight at the end, since that is + really in the next day. */ + if (hour == 24) + break; + + /* We want to finish on upper_hour, with min == 0. */ + for (min = 0; + min == 0 || (min < 60 && hour != priv->upper_hour); + min += 30) { + tmp_tm.tm_hour = hour; + tmp_tm.tm_min = min; + + if (priv->use_24_hour_format) + format = "%H:%M"; + else + format = "%I:%M %p"; + + strftime (buffer, sizeof (buffer), format, &tmp_tm); + + listitem = gtk_list_item_new_with_label (buffer); + gtk_widget_show (listitem); + gtk_container_add (GTK_CONTAINER (list), listitem); + } + } +} + + +static void +enable_time_combo (EDateEdit *dedit) +{ + gtk_widget_set_sensitive (dedit->_priv->time_combo, TRUE); +} + + +static void +disable_time_combo (EDateEdit *dedit) +{ + gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (dedit->_priv->time_combo)->entry), ""); + gtk_widget_set_sensitive (dedit->_priv->time_combo, FALSE); +} + + +static gboolean +date_is_none (char *date_text) +{ + char *pos, *none_string; + + pos = date_text; + while (isspace (*pos)) + pos++; + + none_string = _("None"); + + if (*pos == '\0' || !strncmp (pos, none_string, strlen (none_string))) + return TRUE; + return FALSE; +} + diff --git a/widgets/misc/e-dateedit.h b/widgets/misc/e-dateedit.h new file mode 100644 index 0000000000..a2783c1814 --- /dev/null +++ b/widgets/misc/e-dateedit.h @@ -0,0 +1,110 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * Copyright 2000, Helix Code, Inc. + * + * Based on the GnomeDateEdit, part of the Gnome Library. + * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation + * + * 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 + */ + +/* + * EDateEdit - a widget based on GnomeDateEdit to provide a date & optional + * time field with popups for entering a date. + */ + +#ifndef __E_DATE_EDIT_H_ +#define __E_DATE_EDIT_H_ + +#include <gtk/gtkhbox.h> +#include <libgnome/gnome-defs.h> + +BEGIN_GNOME_DECLS + + +#define E_TYPE_DATE_EDIT (e_date_edit_get_type ()) +#define E_DATE_EDIT(obj) (GTK_CHECK_CAST ((obj), E_TYPE_DATE_EDIT, EDateEdit)) +#define E_DATE_EDIT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_DATE_EDIT, EDateEditClass)) +#define E_IS_DATE_EDIT(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_DATE_EDIT)) +#define E_IS_DATE_EDIT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), E_TYPE_DATE_EDIT)) + + +typedef struct _EDateEdit EDateEdit; +typedef struct _EDateEditPrivate EDateEditPrivate; +typedef struct _EDateEditClass EDateEditClass; + +struct _EDateEdit { + GtkHBox hbox; + + /*< private >*/ + EDateEditPrivate *_priv; +}; + +struct _EDateEditClass { + GtkHBoxClass parent_class; + + void (*date_changed) (EDateEdit *dedit); + void (*time_changed) (EDateEdit *dedit); +}; + +guint e_date_edit_get_type (void); +GtkWidget* e_date_edit_new (void); + +time_t e_date_edit_get_time (EDateEdit *dedit); +void e_date_edit_set_time (EDateEdit *dedit, + time_t the_time); + +/* Whether we show the time field. */ +gboolean e_date_edit_get_show_time (EDateEdit *dedit); +void e_date_edit_set_show_time (EDateEdit *dedit, + gboolean show_time); + +/* The week start day, used in the date popup. 0 (Mon) to 6 (Sun). */ +gint e_date_edit_get_week_start_day (EDateEdit *dedit); +void e_date_edit_set_week_start_day (EDateEdit *dedit, + gint week_start_day); + +/* Whether we show week numbers in the date popup. */ +gboolean e_date_edit_get_show_week_numbers (EDateEdit *dedit); +void e_date_edit_set_show_week_numbers (EDateEdit *dedit, + gboolean show_week_numbers); + +/* Whether we use 24 hour format in the time field & popup. */ +gboolean e_date_edit_get_use_24_hour_format (EDateEdit *dedit); +void e_date_edit_set_use_24_hour_format (EDateEdit *dedit, + gboolean use_24_hour_format); + +/* Whether we allow the date to be set to 'None'. e_date_edit_get_time() will + return (time_t) -1 in this case. */ +gboolean e_date_edit_get_allow_no_date_set (EDateEdit *dedit); +void e_date_edit_set_allow_no_date_set (EDateEdit *dedit, + gboolean allow_no_date_set); + +/* The range of time to show in the time combo popup. */ +void e_date_edit_get_time_popup_range (EDateEdit *dedit, + gint *lower_hour, + gint *upper_hour); +void e_date_edit_set_time_popup_range (EDateEdit *dedit, + gint lower_hour, + gint upper_hour); + +END_GNOME_DECLS + +#endif diff --git a/widgets/misc/test-dateedit.c b/widgets/misc/test-dateedit.c new file mode 100644 index 0000000000..12fb93ac88 --- /dev/null +++ b/widgets/misc/test-dateedit.c @@ -0,0 +1,114 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * Copyright 2000, Helix Code, 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 + */ + +/* + * test-dateedit - tests the EDateEdit widget. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gnome.h> + +#include "e-dateedit.h" + +static void delete_event_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data); +static void on_get_date_clicked (GtkWidget *button, + EDateEdit *dedit); + +int +main (int argc, char **argv) +{ + GtkWidget *app; + EDateEdit *dedit; + GtkWidget *vbox, *button; + + gnome_init ("test-dateedit", "0.0", argc, argv); + + app = gnome_app_new ("Test", "Test"); + gtk_window_set_default_size (GTK_WINDOW (app), 400, 200); + gtk_window_set_policy (GTK_WINDOW (app), FALSE, TRUE, TRUE); + gtk_container_set_border_width (GTK_CONTAINER (app), 8); + + gtk_signal_connect (GTK_OBJECT (app), "delete_event", + GTK_SIGNAL_FUNC (delete_event_cb), NULL); + + dedit = E_DATE_EDIT (e_date_edit_new ()); + gtk_widget_show ((GtkWidget*) (dedit)); + + e_date_edit_set_week_start_day (dedit, 1); + e_date_edit_set_show_week_numbers (dedit, TRUE); + e_date_edit_set_use_24_hour_format (dedit, FALSE); + e_date_edit_set_time_popup_range (dedit, 8, 18); +#if 0 + e_date_edit_set_show_time (dedit, FALSE); +#endif + + vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), (GtkWidget*) dedit, + FALSE, TRUE, 0); + gtk_widget_show (vbox); + + button = gtk_button_new_with_label ("Get Date"); + gtk_box_pack_end (GTK_BOX (vbox), button, FALSE, TRUE, 0); + gtk_widget_show (button); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (on_get_date_clicked), dedit); + + + gnome_app_set_contents (GNOME_APP (app), vbox); + gtk_widget_show (app); + + gtk_main (); + + return 0; +} + + +static void +delete_event_cb (GtkWidget *widget, + GdkEventAny *event, + gpointer data) +{ + gtk_main_quit (); +} + + +static void +on_get_date_clicked (GtkWidget *button, + EDateEdit *dedit) +{ + time_t t; + + t = e_date_edit_get_time (dedit); + + if (t == -1) + g_print ("Time: None\n"); + else + g_print ("Time: %s", ctime (&t)); +} + |