Files
evolution/mail/message-list.c
Christopher James Lahey 32471accad From widgets/e-table/ChangeLog
2000-05-14  Christopher James Lahey  <clahey@helixcode.com>

	* Implemented the feature where the ETable columns automatically
	fill the given space.

	* e-cell-text.c, e-cell-text.h: Moved #include
	e-text-event-processor.h from the .h to the .c.

	* e-table-col.c, e-table-col.h: Added an expansion variable, and
	made it so that width isn't set by the programmer but instead by
	the e-table-header.

	* e-table-example-1.c, e-table-example-2.c, e-table-size-test.c,
	test-check.c, test-cols.c, test-table.c: Fixed to handle new
	ETable column resizing.

	* e-table-group-container.c, e-table-group-container.h,
	e-table-group-leaf.c, e-table-group-leaf.h, e-table-group.c,
	e-table-group.h, e-table-item.c, e-table-item.h: Fixed these to do
	a proper canvas reflow/update loop.  Changed them to take a
	minimum width and return a width and a height.

	* e-table-header-item.c, e-table-header-item.h: Made this so that
	it depends on e-table-header.c for deciding the actual size of
	columns during resize (it was making incorrect decisions on its
	own.)

	* e-table-header.c, e-table-header.h: Changed this to make sure
	that the sum of the widths of the columns was always as close as
	possible to the width of the window.  This is done by taking a
	full width and having each of the columns have an "expansion"
	field.  This field is what makes each column have approximately
	the same portion of its part of the screen that it used to.

	* e-table.c: Changed this to set the width on the ETableHeader as
	well as set the proper minimum width on the ETableGroup and get
	the width and height it reports.

From addressbook/ChangeLog

2000-05-14  Christopher James Lahey  <clahey@helixcode.com>

	* backend/ebook/Makefile.am: Added libeutil for e-card's support
	for categories.

	* backend/ebook/e-card-list.c, backend/ebook/e-card-list.h: Added
	a function to get the length.

	* backend/ebook/e-card.c, backend/ebook/e-card.h: Added categories
	support (accessible either as "categories" or "category_list".)

	* contact-editor/Makefile.am: Added e-table and all of the
	categories files.

	* contact-editor/categories.glade,
	contact-editor/categories-strings.h,
	contact-editor/e-contact-editor-categories.c,
	contact-editor/e-contact-editor-categories.h:

	* contact-editor/contact-editor.glade,
	contact-editor/e-contact-editor-strings.h: Rearranged this dialog.

	* contact-editor/e-contact-editor.c: Rearranged dialog a bit.
	Added opening of categories dialog.

	* gui/component/Makefile.am: Rearranged libraries so that
	libetable would be available for the contact editor categories
	dialog.

	* gui/component/addressbook.c: Fix for new ETable resizing.  Make
	contact editor dialog resizable.

	* gui/minicard/Makefile.am: Added libetable contact editor
	categories dialog.

	* gui/minicard/e-minicard.c: Make contact editor dialog resizable.

From mail/ChangeLog

2000-05-14  Christopher James Lahey  <clahey@helixcode.com>

	* message-list.c: Updated to work with new ETable resizing.

svn path=/trunk/; revision=3027
2000-05-14 14:31:22 +00:00

826 lines
20 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* message-list.c: Displays the messages.
* Implements CORBA's Evolution::MessageList
*
* Author:
* Miguel de Icaza (miguel@helixcode.com)
* Bertrand Guiheneuf (bg@aful.org)
*
* (C) 2000 Helix Code, Inc.
*/
#include <config.h>
#include <gnome.h>
#include <bonobo/bonobo-main.h>
#include "e-util/e-util.h"
#include "camel/camel-exception.h"
#include <camel/camel-folder.h>
#include "message-list.h"
#include "Mail.h"
#include "widgets/e-table/e-table-header-item.h"
#include "widgets/e-table/e-table-item.h"
#include "pixmaps.h"
/*
* Default sizes for the ETable display
*
*/
#define N_CHARS(x) (CHAR_WIDTH * (x))
#define COL_ICON_WIDTH (16)
#define COL_CHECK_BOX_WIDTH (16)
#define COL_FROM_EXPANSION (24.0)
#define COL_FROM_WIDTH_MIN (32)
#define COL_SUBJECT_EXPANSION (30.0)
#define COL_SUBJECT_WIDTH_MIN (32)
#define COL_SENT_EXPANSION (4.0)
#define COL_SENT_WIDTH_MIN (1)
#define COL_RECEIVE_EXPANSION (20.0)
#define COL_RECEIVE_WIDTH_MIN (32)
#define COL_TO_EXPANSION (24.0)
#define COL_TO_WIDTH_MIN (32)
#define COL_SIZE_EXPANSION (6.0)
#define COL_SIZE_WIDTH_MIN (32)
#define PARENT_TYPE (bonobo_object_get_type ())
static BonoboObjectClass *message_list_parent_class;
static POA_Evolution_MessageList__vepv evolution_message_list_vepv;
static void
on_row_selection_cmd (ETable *table,
int row,
gboolean selected,
gpointer user_data);
CamelMessageInfo *get_message_info(MessageList *message_list, gint row)
{
CamelMessageInfo *info = NULL;
if (message_list->search) {
if (row<message_list->match_count) {
info = message_list->summary_search_cache->pdata[row];
if (info == NULL) {
char *uid = g_list_nth_data(message_list->matches, row);
if (uid) {
info = message_list->summary_search_cache->pdata[row] =
camel_folder_summary_get_by_uid(message_list->folder, uid);
}
}
}
} else {
if (row<message_list->summary_table->len)
info = message_list->summary_table->pdata[row];
}
return info;
}
static void
message_changed(CamelMimeMessage *m, enum _MessageChangeType type, MessageList *message_list)
{
e_table_model_changed (message_list->table_model);
}
/* select a message and display it */
static void
select_msg (MessageList *message_list, gint row)
{
CamelException ex;
CamelMimeMessage *message = NULL;
camel_exception_init (&ex);
if (camel_folder_has_uid_capability (message_list->folder)) {
CamelMessageInfo *msg_info;
msg_info = get_message_info(message_list, row);
if (msg_info) {
message = camel_folder_get_message_by_uid (message_list->folder,
msg_info->uid,
&ex);
if (camel_exception_get_id (&ex)) {
printf ("Unable to get message: %s\n",
ex.desc?ex.desc:"unknown_reason");
return;
}
}
if (message) {
gtk_signal_connect((GtkObject *)message, "message_changed", message_changed, message_list);
mail_display_set_message (message_list->parent_folder_browser->mail_display,
CAMEL_MEDIUM (message));
gtk_object_unref (GTK_OBJECT (message));
}
}
}
/*
* SimpleTableModel::col_count
*/
static int
ml_col_count (ETableModel *etm, void *data)
{
return COL_LAST;
}
/*
* SimpleTableModel::row_count
*/
static int
ml_row_count (ETableModel *etm, void *data)
{
MessageList *message_list = data;
int v;
if (!message_list->folder) {
return 0;
}
if (message_list->search) {
v = message_list->match_count;
} else {
v = message_list->summary_table->len;
}
/* in the case where no message is available, return 1
* however, cause we want to be able to show a text */
return (v ? v:1);
}
static void *
ml_value_at (ETableModel *etm, int col, int row, void *data)
{
static char buffer [10];
MessageList *message_list = data;
CamelFolder *folder;
CamelMessageInfo *msg_info;
CamelException ex;
void *retval = NULL;
camel_exception_init (&ex);
folder = message_list->folder;
if (!folder)
goto nothing_to_see;
/* retrieve the message information array */
msg_info = get_message_info(message_list, row);
/*
* in the case where it is zero message long
* display nothing
*/
if (msg_info == NULL)
goto nothing_to_see;
switch (col){
case COL_ONLINE_STATUS:
retval = GINT_TO_POINTER (0);
break;
case COL_MESSAGE_STATUS:
if (msg_info->flags & CAMEL_MESSAGE_DELETED)
retval = GINT_TO_POINTER (2);
else
retval = GINT_TO_POINTER (1);
break;
case COL_PRIORITY:
retval = GINT_TO_POINTER (1);
break;
case COL_ATTACHMENT:
retval = GINT_TO_POINTER (0);
break;
case COL_FROM:
if (msg_info->from)
retval = msg_info->from;
else
retval = "";
break;
case COL_SUBJECT:
if (msg_info->subject)
retval = msg_info->subject;
else
retval = "";
break;
case COL_SENT:
retval = "sent";
break;
case COL_RECEIVE:
retval = "receive";
break;
case COL_TO:
retval = "dudes@server";
break;
case COL_SIZE:
sprintf (buffer, "%d", msg_info->size);
retval = buffer;
break;
default:
g_assert_not_reached ();
}
return retval;
nothing_to_see:
/*
* in the case there is nothing to look at,
* notify the user.
*/
if (col == COL_SUBJECT)
return "No item in this view";
else
return NULL;
}
static void
ml_set_value_at (ETableModel *etm, int col, int row, const void *value, void *data)
{
}
static gboolean
ml_is_cell_editable (ETableModel *etm, int col, int row, void *data)
{
return FALSE;
}
static void *
ml_duplicate_value (ETableModel *etm, int col, const void *value, void *data)
{
switch (col){
case COL_ONLINE_STATUS:
case COL_MESSAGE_STATUS:
case COL_PRIORITY:
case COL_ATTACHMENT:
return (void *) value;
case COL_FROM:
case COL_SUBJECT:
case COL_SENT:
case COL_RECEIVE:
case COL_TO:
case COL_SIZE:
return g_strdup (value);
default:
g_assert_not_reached ();
}
return NULL;
}
static void
ml_free_value (ETableModel *etm, int col, void *value, void *data)
{
switch (col){
case COL_ONLINE_STATUS:
case COL_MESSAGE_STATUS:
case COL_PRIORITY:
case COL_ATTACHMENT:
break;
case COL_FROM:
case COL_SUBJECT:
case COL_SENT:
case COL_RECEIVE:
case COL_TO:
case COL_SIZE:
g_free (value);
break;
default:
g_assert_not_reached ();
}
}
static void
ml_thaw (ETableModel *etm, void *data)
{
e_table_model_changed (etm);
}
static struct {
char **image_base;
GdkPixbuf *pixbuf;
} states_pixmaps [] = {
{ envelope_opened_xpm, NULL },
{ envelope_closed_xpm, NULL },
{ empty_xpm, NULL },
{ attachment_xpm, NULL },
{ attachment_header_xpm, NULL },
{ online_status_xpm, NULL },
{ message_status_xpm, NULL },
{ envelope_deleted_xpm, NULL },
{ NULL, NULL },
};
static void
message_list_init_images (void)
{
int i;
/*
* Only load once, and share
*/
if (states_pixmaps [0].pixbuf)
return;
for (i = 0; states_pixmaps [i].image_base; i++){
states_pixmaps [i].pixbuf = gdk_pixbuf_new_from_xpm_data (
(const char **) states_pixmaps [i].image_base);
}
}
static void
message_list_init_renderers (MessageList *message_list)
{
g_assert (message_list);
g_assert (message_list->table_model);
message_list->render_text = e_cell_text_new (
message_list->table_model,
NULL, GTK_JUSTIFY_LEFT);
message_list->render_online_status = e_cell_checkbox_new ();
/*
* Message status
*/
{
GdkPixbuf *images [3];
images [0] = states_pixmaps [0].pixbuf;
images [1] = states_pixmaps [1].pixbuf;
images [2] = states_pixmaps [7].pixbuf;
message_list->render_message_status = e_cell_toggle_new (0, 3, images);
}
/*
* Attachment
*/
{
GdkPixbuf *images [2];
images [0] = states_pixmaps [2].pixbuf;
images [1] = states_pixmaps [3].pixbuf;
message_list->render_attachment = e_cell_toggle_new (0, 2, images);
}
/*
* FIXME: We need a real renderer here
*/
message_list->render_priority = e_cell_checkbox_new ();
}
static void
message_list_init_header (MessageList *message_list)
{
int i;
/*
* FIXME:
*
* Use the font metric to compute this.
*/
message_list->header_model = e_table_header_new ();
gtk_object_ref (GTK_OBJECT (message_list->header_model));
gtk_object_sink (GTK_OBJECT (message_list->header_model));
message_list->table_cols [COL_ONLINE_STATUS] =
e_table_col_new_with_pixbuf (
COL_ONLINE_STATUS, states_pixmaps [5].pixbuf,
0.0, COL_CHECK_BOX_WIDTH,
message_list->render_online_status,
g_int_compare, FALSE);
message_list->table_cols [COL_MESSAGE_STATUS] =
e_table_col_new_with_pixbuf (
COL_MESSAGE_STATUS, states_pixmaps [0].pixbuf,
0.0, COL_CHECK_BOX_WIDTH,
message_list->render_message_status,
g_int_compare, FALSE);
message_list->table_cols [COL_PRIORITY] =
e_table_col_new (
COL_PRIORITY, _("Priority"),
0.0, COL_CHECK_BOX_WIDTH,
message_list->render_priority,
g_int_compare, FALSE);
message_list->table_cols [COL_ATTACHMENT] =
e_table_col_new_with_pixbuf (
COL_ATTACHMENT, states_pixmaps [4].pixbuf,
0.0, COL_ICON_WIDTH,
message_list->render_attachment,
g_int_compare, FALSE);
message_list->table_cols [COL_FROM] =
e_table_col_new (
COL_FROM, _("From"),
COL_FROM_EXPANSION, COL_FROM_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
message_list->table_cols [COL_SUBJECT] =
e_table_col_new (
COL_SUBJECT, _("Subject"),
COL_SUBJECT_EXPANSION, COL_SUBJECT_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
message_list->table_cols [COL_SENT] =
e_table_col_new (
COL_SENT, _("Sent"),
COL_SENT_EXPANSION, COL_SENT_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
message_list->table_cols [COL_RECEIVE] =
e_table_col_new (
COL_RECEIVE, _("Receive"),
COL_RECEIVE_EXPANSION, COL_RECEIVE_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
message_list->table_cols [COL_TO] =
e_table_col_new (
COL_TO, _("To"),
COL_TO_EXPANSION, COL_TO_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
message_list->table_cols [COL_SIZE] =
e_table_col_new (
COL_SIZE, _("Size"),
COL_SIZE_EXPANSION, COL_SIZE_WIDTH_MIN,
message_list->render_text,
g_str_compare, TRUE);
/*
* Dummy init: It setups the headers to match the order in which
* they are defined. In the future e-table widget will take care
* of this.
*/
for (i = 0; i < COL_LAST; i++) {
gtk_object_ref (GTK_OBJECT (message_list->table_cols [i]));
e_table_header_add_column (message_list->header_model,
message_list->table_cols [i], i);
}
}
static char *
message_list_get_layout (MessageList *message_list)
{
if (0)
return g_strdup ("<ETableSpecification> <columns-shown> <column> 0 </column> <column> 1 </column> <column> 2 </column> <column> 3 </column> <column> 4 </column> <column> 5 </column> <column> 6 </column> <column> 7 </column> <column> 8 </column> <column> 9 </column> </columns-shown> <grouping> <group column=\"4\" ascending=\"1\"> <leaf column=\"5\" ascending=\"1\"/> </group> </grouping> </ETableSpecification>");
else {
/* Message status, From, Sent, Subject */
return g_strdup ("<ETableSpecification> <columns-shown> <column> 1 </column> <column> 4 </column> <column> 5 </column> </columns-shown> <grouping> </grouping> </ETableSpecification>");
}
}
/*
* GtkObject::init
*/
static void
message_list_init (GtkObject *object)
{
MessageList *message_list = MESSAGE_LIST (object);
char *spec;
message_list->table_model = e_table_simple_new (
ml_col_count, ml_row_count, ml_value_at,
ml_set_value_at, ml_is_cell_editable, ml_duplicate_value, ml_free_value,
ml_thaw, message_list);
message_list_init_renderers (message_list);
message_list_init_header (message_list);
/*
* The etable
*/
spec = message_list_get_layout (message_list);
message_list->etable = e_table_new (
message_list->header_model, message_list->table_model, spec);
g_free (spec);
gtk_signal_connect (GTK_OBJECT (message_list->etable), "row_selection",
GTK_SIGNAL_FUNC (on_row_selection_cmd), message_list);
gtk_widget_show (message_list->etable);
gtk_object_ref (GTK_OBJECT (message_list->table_model));
gtk_object_sink (GTK_OBJECT (message_list->table_model));
/*
* We do own the Etable, not some widget container
*/
gtk_object_ref (GTK_OBJECT (message_list->etable));
gtk_object_sink (GTK_OBJECT (message_list->etable));
message_list->summary_search_cache = g_ptr_array_new();
}
static void
message_list_destroy (GtkObject *object)
{
MessageList *message_list = MESSAGE_LIST (object);
int i;
gtk_object_unref (GTK_OBJECT (message_list->table_model));
gtk_object_unref (GTK_OBJECT (message_list->header_model));
/*
* Renderers
*/
gtk_object_unref (GTK_OBJECT (message_list->render_text));
gtk_object_unref (GTK_OBJECT (message_list->render_online_status));
gtk_object_unref (GTK_OBJECT (message_list->render_message_status));
gtk_object_unref (GTK_OBJECT (message_list->render_priority));
gtk_object_unref (GTK_OBJECT (message_list->render_attachment));
gtk_object_unref (GTK_OBJECT (message_list->etable));
if (message_list->summary_search_cache)
g_ptr_array_free(message_list->summary_search_cache, TRUE);
if (message_list->summary_table)
g_ptr_array_free(message_list->summary_table, TRUE);
for (i = 0; i < COL_LAST; i++)
gtk_object_unref (GTK_OBJECT (message_list->table_cols [i]));
GTK_OBJECT_CLASS (message_list_parent_class)->destroy (object);
}
/*
* CORBA method: Evolution::MessageList::select_message
*/
static void
MessageList_select_message (PortableServer_Servant _servant,
const CORBA_long message_number,
CORBA_Environment *ev)
{
printf ("FIXME: select message method\n");
}
/*
* CORBA method: Evolution::MessageList::open_message
*/
static void
MessageList_open_message (PortableServer_Servant _servant,
const CORBA_long message_number,
CORBA_Environment *ev)
{
printf ("FIXME: open message method\n");
}
static POA_Evolution_MessageList__epv *
evolution_message_list_get_epv (void)
{
POA_Evolution_MessageList__epv *epv;
epv = g_new0 (POA_Evolution_MessageList__epv, 1);
epv->select_message = MessageList_select_message;
epv->open_message = MessageList_open_message;
return epv;
}
static void
message_list_corba_class_init (void)
{
evolution_message_list_vepv.Bonobo_Unknown_epv = bonobo_object_get_epv ();
evolution_message_list_vepv.Evolution_MessageList_epv = evolution_message_list_get_epv ();
}
/*
* GtkObjectClass::init
*/
static void
message_list_class_init (GtkObjectClass *object_class)
{
message_list_parent_class = gtk_type_class (PARENT_TYPE);
object_class->destroy = message_list_destroy;
message_list_corba_class_init ();
message_list_init_images ();
}
static void
message_list_construct (MessageList *message_list, Evolution_MessageList corba_message_list)
{
bonobo_object_construct (BONOBO_OBJECT (message_list), corba_message_list);
}
static Evolution_MessageList
create_corba_message_list (BonoboObject *object)
{
POA_Evolution_MessageList *servant;
CORBA_Environment ev;
servant = (POA_Evolution_MessageList *) g_new0 (BonoboObjectServant, 1);
servant->vepv = &evolution_message_list_vepv;
CORBA_exception_init (&ev);
POA_Evolution_MessageList__init ((PortableServer_Servant) servant, &ev);
if (ev._major != CORBA_NO_EXCEPTION){
g_free (servant);
CORBA_exception_free (&ev);
return CORBA_OBJECT_NIL;
}
CORBA_exception_free (&ev);
return (Evolution_MessageList) bonobo_object_activate_servant (object, servant);
}
BonoboObject *
message_list_new (FolderBrowser *parent_folder_browser)
{
Evolution_MessageList corba_object;
MessageList *message_list;
g_assert (parent_folder_browser);
message_list = gtk_type_new (message_list_get_type ());
corba_object = create_corba_message_list (BONOBO_OBJECT (message_list));
if (corba_object == CORBA_OBJECT_NIL){
gtk_object_destroy (GTK_OBJECT (message_list));
return NULL;
}
message_list->parent_folder_browser = parent_folder_browser;
message_list->idle_id = 0;
message_list_construct (message_list, corba_object);
return BONOBO_OBJECT (message_list);
}
void
message_list_set_search (MessageList *message_list, const char *search)
{
if (message_list->matches) {
/* FIXME: free contents too ... */
g_list_free(message_list->matches);
message_list->matches = NULL;
}
if (message_list->search) {
g_free(message_list->search);
message_list->search = NULL;
}
if (search) {
CamelException ex;
camel_exception_init (&ex);
message_list->matches = camel_folder_search_by_expression(message_list->folder, search, &ex);
message_list->search = g_strdup(search);
message_list->match_count = g_list_length(message_list->matches);
g_ptr_array_set_size(message_list->summary_search_cache, message_list->match_count);
memset(message_list->summary_search_cache->pdata, 0, sizeof(message_list->summary_search_cache->pdata[0]) * message_list->match_count);
}
e_table_model_changed (message_list->table_model);
select_msg (message_list, 0);
}
static void
folder_changed(CamelFolder *f, int type, MessageList *message_list)
{
if (message_list->summary_table)
g_ptr_array_free(message_list->summary_table, TRUE);
message_list->summary_table = camel_folder_summary_get_message_info (message_list->folder, 0, INT_MAX);
message_list_set_search(message_list, message_list->search);
}
void
message_list_set_folder (MessageList *message_list, CamelFolder *camel_folder)
{
CamelException ex;
gboolean folder_exists;
g_return_if_fail (message_list != NULL);
g_return_if_fail (camel_folder != NULL);
g_return_if_fail (IS_MESSAGE_LIST (message_list));
g_return_if_fail (CAMEL_IS_FOLDER (camel_folder));
g_return_if_fail (camel_folder_has_summary_capability (camel_folder));
if (message_list->matches) {
/* FIXME: free contents too ... */
g_list_free(message_list->matches);
message_list->matches = NULL;
}
if (message_list->summary_table)
g_ptr_array_free(message_list->summary_table, TRUE);
message_list->summary_table = NULL;
camel_exception_init (&ex);
if (message_list->folder)
gtk_object_unref (GTK_OBJECT (message_list->folder));
message_list->folder = camel_folder;
folder_exists = camel_folder_exists (camel_folder, NULL);
if (camel_exception_get_id (&ex)) {
printf ("Unable to test for folder existence: %s\n",
ex.desc?ex.desc:"unknown reason");
return;
}
if (!folder_exists) {
g_warning ("Folder does not exist, creating it\n");
/*
if you don't want the directory to be created
automatically here remove this.
*/
camel_folder_create (camel_folder, &ex);
if (camel_exception_get_id (&ex)) {
printf ("Unable to create folder: %s\n",
ex.desc?ex.desc:"unknown_reason");
return;
}
}
camel_folder_open (camel_folder, FOLDER_OPEN_RW, &ex);
if (camel_exception_get_id (&ex)) {
printf ("Unable to open folder: %s\n",
ex.desc?ex.desc:"unknown_reason");
return;
}
gtk_signal_connect((GtkObject *)camel_folder, "folder_changed", folder_changed, message_list);
gtk_object_ref (GTK_OBJECT (camel_folder));
message_list->summary_table = camel_folder_summary_get_message_info (message_list->folder, 0, INT_MAX);
e_table_model_changed (message_list->table_model);
select_msg (message_list, 0);
}
GtkWidget *
message_list_get_widget (MessageList *message_list)
{
return message_list->etable;
}
E_MAKE_TYPE (message_list, "MessageList", MessageList, message_list_class_init, message_list_init, PARENT_TYPE);
static gboolean
on_row_selection_idle (gpointer data)
{
MessageList *message_list = data;
select_msg (message_list, message_list->row_to_select);
message_list->idle_id = 0;
return FALSE;
}
static void
on_row_selection_cmd (ETable *table,
int row,
gboolean selected,
gpointer user_data)
{
if (selected) {
MessageList *message_list;
message_list = MESSAGE_LIST (user_data);
message_list->row_to_select = row;
if (!message_list->idle_id)
g_idle_add_full (G_PRIORITY_LOW, on_row_selection_idle, message_list, NULL);
}
}