/* Evolution calendar - Data model for ETable * * Copyright (C) 2000 Helix Code, Inc. * * Authors: Federico Mena-Quintero * * 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. */ #include #include #include "calendar-model.h" /* Private part of the ECalendarModel structure */ typedef struct { /* Calendar client we are using */ CalClient *client; /* Types of objects we are dealing with */ CalObjType type; /* Array of pointers to calendar objects */ GArray *objects; /* UID -> array index hash */ GHashTable *uid_index_hash; } CalendarModelPrivate; static void calendar_model_class_init (CalendarModelClass *class); static void calendar_model_init (CalendarModel *model); static void calendar_model_destroy (GtkObject *object); static int calendar_model_column_count (ETableModel *etm); static int calendar_model_row_count (ETableModel *etm); static void *calendar_model_value_at (ETableModel *etm, int col, int row); static void calendar_model_set_value_at (ETableModel *etm, int col, int row, const void *value); static gboolean calendar_model_is_cell_editable (ETableModel *etm, int col, int row); static void *calendar_model_duplicate_value (ETableModel *etm, int col, const void *value); static void calendar_model_free_value (ETableModel *etm, int col, void *value); static void *calendar_model_initialize_value (ETableModel *etm, int col); static gboolean calendar_model_value_is_empty (ETableModel *etm, int col, const void *value); static char * calendar_model_value_to_string (ETableModel *etm, int col, const void *value); static ETableModelClass *parent_class; /** * calendar_model_get_type: * @void: * * Registers the #CalendarModel class if necessary, and returns the type ID * associated to it. * * Return value: The type ID of the #CalendarModel class. **/ GtkType calendar_model_get_type (void) { static GtkType calendar_model_type = 0; if (!calendar_model_type) { static GtkTypeInfo calendar_model_info = { "CalendarModel", sizeof (CalendarModel), sizeof (CalendarModelClass), (GtkClassInitFunc) calendar_model_class_init, (GtkObjectInitFunc) calendar_model_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; calendar_model_type = gtk_type_unique (E_TABLE_MODEL_TYPE, &calendar_model_info); } return calendar_model_type; } /* Class initialization function for the calendar table model */ static void calendar_model_class_init (CalendarModelClass *class) { GtkObjectClass *object_class; ETableModelClass *etm_class; object_class = (GtkObjectClass *) class; etm_class = (ETableModelClass *) class; parent_class = gtk_type_class (E_TABLE_MODEL_TYPE); object_class->destroy = calendar_model_destroy; etm_class->column_count = calendar_model_column_count; etm_class->row_count = calendar_model_row_count; etm_class->value_at = calendar_model_value_at; etm_class->set_value_at = calendar_model_set_value_at; etm_class->is_cell_editable = calendar_model_is_cell_editable; etm_class->duplicate_value = calendar_model_duplicate_value; etm_class->free_value = calendar_model_free_value; etm_class->initialize_value = calendar_model_initialize_value; etm_class->value_is_empty = calendar_model_value_is_empty; #if 0 etm_class->value_to_string = calendar_model_value_to_string; #endif } /* Object initialization function for the calendar table model */ static void calendar_model_init (CalendarModel *model) { CalendarModelPrivate *priv; priv = g_new0 (CalendarModelPrivate, 1); model->priv = priv; priv->objects = g_array_new (FALSE, TRUE, sizeof (iCalObject *)); priv->uid_index_hash = g_hash_table_new (g_str_hash, g_str_equal); } /* Called from g_hash_table_foreach_remove(), frees a stored UID->index * mapping. */ static gboolean free_uid_index (gpointer key, gpointer value, gpointer data) { int *idx; idx = value; g_free (idx); return TRUE; } /* Frees the objects stored in the calendar model */ static void free_objects (CalendarModel *model) { CalendarModelPrivate *priv; int i; priv = model->priv; g_hash_table_foreach_remove (priv->uid_index_hash, free_uid_index, NULL); for (i = 0; i < priv->objects->len; i++) { iCalObject *ico; ico = g_array_index (priv->objects, iCalObject *, i); g_assert (ico != NULL); ical_object_unref (ico); } g_array_set_size (priv->objects, 0); } /* Destroy handler for the calendar table model */ static void calendar_model_destroy (GtkObject *object) { CalendarModel *model; CalendarModelPrivate *priv; g_return_if_fail (object != NULL); g_return_if_fail (IS_CALENDAR_MODEL (object)); model = CALENDAR_MODEL (object); priv = model->priv; /* Free the calendar client interface object */ if (priv->client) { gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), model); gtk_object_unref (GTK_OBJECT (priv->client)); priv->client = NULL; } /* Free the uid->index hash data and the array of UIDs */ free_objects (model); g_hash_table_destroy (priv->uid_index_hash); priv->uid_index_hash = NULL; g_array_free (priv->objects, TRUE); priv->objects = NULL; /* Free the private structure */ g_free (priv); model->priv = NULL; if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } /* ETableModel methods */ /* column_count handler for the calendar table model */ static int calendar_model_column_count (ETableModel *etm) { return ICAL_OBJECT_FIELD_NUM_FIELDS; } /* row_count handler for the calendar table model */ static int calendar_model_row_count (ETableModel *etm) { CalendarModel *model; CalendarModelPrivate *priv; model = CALENDAR_MODEL (etm); priv = model->priv; return priv->objects->len; } /* value_at handler for the calendar table model */ static void * calendar_model_value_at (ETableModel *etm, int col, int row) { CalendarModel *model; CalendarModelPrivate *priv; iCalObject *ico; model = CALENDAR_MODEL (etm); priv = model->priv; g_return_val_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS, NULL); g_return_val_if_fail (row >= 0 && row < priv->objects->len, NULL); ico = g_array_index (priv->objects, iCalObject *, row); g_assert (ico != NULL); switch (col) { case ICAL_OBJECT_FIELD_COMMENT: return ico->comment ? ico->comment : ""; case ICAL_OBJECT_FIELD_COMPLETED: return &ico->completed; case ICAL_OBJECT_FIELD_CREATED: return &ico->created; case ICAL_OBJECT_FIELD_DESCRIPTION: return ico->desc ? ico->desc : ""; case ICAL_OBJECT_FIELD_DTSTAMP: return &ico->dtstamp; case ICAL_OBJECT_FIELD_DTSTART: return &ico->dtstart; case ICAL_OBJECT_FIELD_DTEND: return &ico->dtend; case ICAL_OBJECT_FIELD_GEO: return &ico->geo; case ICAL_OBJECT_FIELD_LAST_MOD: return &ico->last_mod; case ICAL_OBJECT_FIELD_LOCATION: return ico->location ? ico->location : ""; case ICAL_OBJECT_FIELD_ORGANIZER: return ico->organizer; case ICAL_OBJECT_FIELD_PERCENT: return &ico->percent; case ICAL_OBJECT_FIELD_PRIORITY: return &ico->priority; case ICAL_OBJECT_FIELD_SUMMARY: return ico->summary ? ico->summary : ""; case ICAL_OBJECT_FIELD_URL: return ico->url ? ico->url : ""; case ICAL_OBJECT_FIELD_HAS_ALARMS: return (gpointer) (ico->dalarm.enabled || ico->aalarm.enabled || ico->palarm.enabled || ico->malarm.enabled); default: g_message ("calendar_model_value_at(): Requested invalid column %d", col); return NULL; } } /* Replaces a string */ static void set_string (char **dest, const char *value) { if (*dest) g_free (*dest); if (value) *dest = g_strdup (value); else *dest = NULL; } /* Replaces a time_t value */ static void set_time_t (time_t *dest, const time_t *value) { *dest = *value; } /* Replaces a geo value */ static void set_geo (iCalGeo *dest, const iCalGeo *value) { *dest = *value; } /* Replaces a person value */ static void set_person (iCalPerson **dest, const iCalPerson *value) { /* FIXME */ } /* Sets an int value */ static void set_int (int *dest, const int *value) { *dest = *value; } /* set_value_at handler for the calendar table model */ static void calendar_model_set_value_at (ETableModel *etm, int col, int row, const void *value) { CalendarModel *model; CalendarModelPrivate *priv; iCalObject *ico; model = CALENDAR_MODEL (etm); priv = model->priv; g_return_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS); g_return_if_fail (row >= 0 && row < priv->objects->len); ico = g_array_index (priv->objects, iCalObject *, row); g_assert (ico != NULL); switch (col) { case ICAL_OBJECT_FIELD_COMMENT: set_string (&ico->comment, value); break; case ICAL_OBJECT_FIELD_COMPLETED: set_time_t (&ico->completed, value); break; case ICAL_OBJECT_FIELD_CREATED: set_time_t (&ico->created, value); break; case ICAL_OBJECT_FIELD_DESCRIPTION: set_string (&ico->desc, value); break; case ICAL_OBJECT_FIELD_DTSTAMP: set_time_t (&ico->dtstamp, value); break; case ICAL_OBJECT_FIELD_DTSTART: set_time_t (&ico->dtstart, value); break; case ICAL_OBJECT_FIELD_DTEND: set_time_t (&ico->dtend, value); break; case ICAL_OBJECT_FIELD_GEO: set_geo (&ico->geo, value); break; case ICAL_OBJECT_FIELD_LAST_MOD: set_time_t (&ico->last_mod, value); break; case ICAL_OBJECT_FIELD_LOCATION: set_string (&ico->location, value); break; case ICAL_OBJECT_FIELD_ORGANIZER: set_person (&ico->organizer, value); break; case ICAL_OBJECT_FIELD_PERCENT: set_int (&ico->percent, value); break; case ICAL_OBJECT_FIELD_PRIORITY: set_int (&ico->priority, value); break; case ICAL_OBJECT_FIELD_SUMMARY: set_string (&ico->summary, value); break; case ICAL_OBJECT_FIELD_URL: set_string (&ico->url, value); break; case ICAL_OBJECT_FIELD_HAS_ALARMS: g_message ("calendar_model_set_value_at(): HAS_ALARMS is not a settable field!"); break; default: g_message ("calendar_model_set_value_at(): Requested invalid column %d", col); break; } if (!cal_client_update_object (priv->client, ico)) g_message ("calendar_model_set_value_at(): Could not update the object!"); } /* is_cell_editable handler for the calendar table model */ static gboolean calendar_model_is_cell_editable (ETableModel *etm, int col, int row) { CalendarModel *model; CalendarModelPrivate *priv; model = CALENDAR_MODEL (etm); priv = model->priv; g_return_val_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS, FALSE); g_return_val_if_fail (row >= 0 && row < priv->objects->len, FALSE); switch (col) { case ICAL_OBJECT_FIELD_HAS_ALARMS: return FALSE; default: return TRUE; } } /* Duplicates a string value */ static char * dup_string (const char *value) { return g_strdup (value); } /* Duplicates a time_t value */ static time_t * dup_time_t (const time_t *value) { time_t *t; t = g_new (time_t, 1); *t = *value; return t; } /* Duplicates a geo value */ static iCalGeo * dup_geo (const iCalGeo *value) { iCalGeo *geo; geo = g_new (iCalGeo, 1); *geo = *value; return geo; } /* Duplicates a person value */ static iCalPerson * dup_person (const iCalPerson *value) { /* FIXME */ return NULL; } /* Duplicates an int value */ static int * dup_int (const int *value) { int *v; v = g_new (int, 1); *v = *value; return v; } /* duplicate_value handler for the calendar table model */ static void * calendar_model_duplicate_value (ETableModel *etm, int col, const void *value) { g_return_val_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS, NULL); switch (col) { case ICAL_OBJECT_FIELD_COMMENT: return dup_string (value); case ICAL_OBJECT_FIELD_COMPLETED: return dup_time_t (value); case ICAL_OBJECT_FIELD_CREATED: return dup_time_t (value); case ICAL_OBJECT_FIELD_DESCRIPTION: return dup_string (value); case ICAL_OBJECT_FIELD_DTSTAMP: return dup_time_t (value); case ICAL_OBJECT_FIELD_DTSTART: return dup_time_t (value); case ICAL_OBJECT_FIELD_DTEND: return dup_time_t (value); case ICAL_OBJECT_FIELD_GEO: return dup_geo (value); case ICAL_OBJECT_FIELD_LAST_MOD: return dup_time_t (value); case ICAL_OBJECT_FIELD_LOCATION: return dup_string (value); case ICAL_OBJECT_FIELD_ORGANIZER: return dup_person (value); case ICAL_OBJECT_FIELD_PERCENT: return dup_int (value); case ICAL_OBJECT_FIELD_PRIORITY: return dup_int (value); case ICAL_OBJECT_FIELD_SUMMARY: return dup_string (value); case ICAL_OBJECT_FIELD_URL: return dup_string (value); case ICAL_OBJECT_FIELD_HAS_ALARMS: return (void *) value; default: g_message ("calendar_model_duplicate_value(): Requested invalid column %d", col); return NULL; } } /* free_value handler for the calendar table model */ static void calendar_model_free_value (ETableModel *etm, int col, void *value) { g_return_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS); g_return_if_fail (value != NULL); /* FIXME: this requires special handling for iCalPerson */ g_free (value); } /* Initializes a string value */ static char * init_string (void) { return g_strdup (""); } /* Initializes a time_t value */ static time_t * init_time_t (void) { time_t *t; t = g_new (time_t, 1); *t = -1; return t; } /* Initializes a geo value */ static iCalGeo * init_geo (void) { iCalGeo *geo; geo = g_new (iCalGeo, 1); geo->valid = FALSE; geo->latitude = 0.0; geo->longitude = 0.0; return geo; } /* Initializes a person value */ static iCalPerson * init_person (void) { /* FIXME */ return NULL; } /* Initializes an int value */ static int * init_int (void) { int *v; v = g_new (int, 1); *v = 0; return v; } /* initialize_value handler for the calendar table model */ static void * calendar_model_initialize_value (ETableModel *etm, int col) { g_return_val_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS, NULL); switch (col) { case ICAL_OBJECT_FIELD_COMMENT: return init_string (); case ICAL_OBJECT_FIELD_COMPLETED: return init_time_t (); case ICAL_OBJECT_FIELD_CREATED: return init_time_t (); case ICAL_OBJECT_FIELD_DESCRIPTION: return init_string (); case ICAL_OBJECT_FIELD_DTSTAMP: return init_time_t (); case ICAL_OBJECT_FIELD_DTSTART: return init_time_t (); case ICAL_OBJECT_FIELD_DTEND: return init_time_t (); case ICAL_OBJECT_FIELD_GEO: return init_geo (); case ICAL_OBJECT_FIELD_LAST_MOD: return init_time_t (); case ICAL_OBJECT_FIELD_LOCATION: return init_string (); case ICAL_OBJECT_FIELD_ORGANIZER: return init_person (); case ICAL_OBJECT_FIELD_PERCENT: return init_int (); case ICAL_OBJECT_FIELD_PRIORITY: return init_int (); case ICAL_OBJECT_FIELD_SUMMARY: return init_string (); case ICAL_OBJECT_FIELD_URL: return init_string (); case ICAL_OBJECT_FIELD_HAS_ALARMS: return NULL; /* "false" */ default: g_message ("calendar_model_initialize_value(): Requested invalid column %d", col); return NULL; } } /* Returns whether a string is empty */ static gboolean string_is_empty (const char *str) { return !(str && *str); } /* Returns whether a time_t is empty */ static gboolean time_t_is_empty (const time_t *t) { return (*t == -1); } /* Returns whether a geo is empty */ static gboolean geo_is_empty (const iCalGeo *geo) { return !geo->valid; } /* Returns whether a person is empty */ static gboolean person_is_empty (const iCalPerson *person) { /* FIXME */ return TRUE; } /* Returns whether an int is empty */ static gboolean int_is_empty (const int *value) { return FALSE; } /* value_is_empty handler for the calendar model */ static gboolean calendar_model_value_is_empty (ETableModel *etm, int col, const void *value) { g_return_val_if_fail (col >= 0 && col < ICAL_OBJECT_FIELD_NUM_FIELDS, TRUE); switch (col) { case ICAL_OBJECT_FIELD_COMMENT: return string_is_empty (value); case ICAL_OBJECT_FIELD_COMPLETED: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_CREATED: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_DESCRIPTION: return string_is_empty (value); case ICAL_OBJECT_FIELD_DTSTAMP: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_DTSTART: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_DTEND: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_GEO: return geo_is_empty (value); case ICAL_OBJECT_FIELD_LAST_MOD: return time_t_is_empty (value); case ICAL_OBJECT_FIELD_LOCATION: return string_is_empty (value); case ICAL_OBJECT_FIELD_ORGANIZER: return person_is_empty (value); case ICAL_OBJECT_FIELD_PERCENT: return int_is_empty (value); case ICAL_OBJECT_FIELD_PRIORITY: return int_is_empty (value); case ICAL_OBJECT_FIELD_SUMMARY: return string_is_empty (value); case ICAL_OBJECT_FIELD_URL: return string_is_empty (value); case ICAL_OBJECT_FIELD_HAS_ALARMS: return FALSE; default: g_message ("calendar_model_value_is_empty(): Requested invalid column %d", col); return TRUE; } } /** * calendar_model_new: * * Creates a new calendar model. It must be told about the calendar client * interface object it will monitor with calendar_model_set_cal_client(). * * Return value: A newly-created calendar model. **/ CalendarModel * calendar_model_new (void) { return CALENDAR_MODEL (gtk_type_new (TYPE_CALENDAR_MODEL)); } /* Removes an object from the model and updates all the indices that follow. * Returns the index of the object that was removed, or -1 if no object with * such UID was found. */ static int remove_object (CalendarModel *model, const char *uid) { CalendarModelPrivate *priv; int *idx; iCalObject *orig_ico; int i; int n; priv = model->priv; /* Find the index of the object to be removed */ idx = g_hash_table_lookup (priv->uid_index_hash, uid); if (!idx) return -1; orig_ico = g_array_index (priv->objects, iCalObject *, *idx); g_assert (orig_ico != NULL); /* Decrease the indices of all the objects that follow in the array */ for (i = *idx + 1; i < priv->objects->len; i++) { iCalObject *ico; int *ico_idx; ico = g_array_index (priv->objects, iCalObject *, i); g_assert (ico != NULL); ico_idx = g_hash_table_lookup (priv->uid_index_hash, ico->uid); g_assert (ico_idx != NULL); (*ico_idx)--; g_assert (*ico_idx >= 0); } /* Remove this object from the array and hash */ g_hash_table_remove (priv->uid_index_hash, uid); g_array_remove_index (priv->objects, *idx); ical_object_unref (orig_ico); n = *idx; g_free (idx); return n; } /* Callback used when an object is updated in the server */ static void obj_updated_cb (CalClient *client, const char *uid, gpointer data) { CalendarModel *model; CalendarModelPrivate *priv; int orig_idx; iCalObject *new_ico; int *new_idx; CalClientGetStatus status; gboolean added; model = CALENDAR_MODEL (data); priv = model->priv; orig_idx = remove_object (model, uid); status = cal_client_get_object (priv->client, uid, &new_ico); added = FALSE; switch (status) { case CAL_CLIENT_GET_SUCCESS: if (orig_idx == -1) { /* The object not in the model originally, so we just append it */ g_array_append_val (priv->objects, new_ico); new_idx = g_new (int, 1); *new_idx = priv->objects->len - 1; g_hash_table_insert (priv->uid_index_hash, new_ico->uid, new_idx); } else { int i; /* Insert the new version of the object in its old position */ g_array_insert_val (priv->objects, orig_idx, new_ico); new_idx = g_new (int, 1); *new_idx = orig_idx; g_hash_table_insert (priv->uid_index_hash, new_ico->uid, new_idx); /* Increase the indices of all subsequent objects */ for (i = orig_idx + 1; i < priv->objects->len; i++) { iCalObject *ico; int *ico_idx; ico = g_array_index (priv->objects, iCalObject *, i); g_assert (ico != NULL); ico_idx = g_hash_table_lookup (priv->uid_index_hash, ico->uid); g_assert (ico_idx != NULL); (*ico_idx)++; } } e_table_model_row_changed (E_TABLE_MODEL (model), *new_idx); break; case CAL_CLIENT_GET_NOT_FOUND: /* Nothing; the object may have been removed from the server. We just * notify that the old object was deleted. */ if (orig_idx != -1) e_table_model_row_deleted (E_TABLE_MODEL (model), orig_idx); break; case CAL_CLIENT_GET_SYNTAX_ERROR: g_message ("obj_updated_cb(): Syntax error when getting object `%s'", uid); /* Same notification as above */ if (orig_idx != -1) e_table_model_row_deleted (E_TABLE_MODEL (model), orig_idx); break; default: g_assert_not_reached (); } } /* Callback used when an object is removed in the server */ static void obj_removed_cb (CalClient *client, const char *uid, gpointer data) { CalendarModel *model; int idx; model = CALENDAR_MODEL (data); idx = remove_object (model, uid); if (idx != -1) e_table_model_row_deleted (E_TABLE_MODEL (model), idx); } /* Loads the required objects from the calendar client */ static void load_objects (CalendarModel *model) { CalendarModelPrivate *priv; GList *uids; GList *l; priv = model->priv; uids = cal_client_get_uids (priv->client, priv->type); for (l = uids; l; l = l->next) { char *uid; iCalObject *ico; CalClientGetStatus status; int *idx; uid = l->data; status = cal_client_get_object (priv->client, uid, &ico); switch (status) { case CAL_CLIENT_GET_SUCCESS: break; case CAL_CLIENT_GET_NOT_FOUND: /* Nothing; the object may have been removed from the server */ continue; case CAL_CLIENT_GET_SYNTAX_ERROR: g_message ("load_objects(): Syntax error when getting object `%s'", uid); continue; default: g_assert_not_reached (); } g_assert (ico->uid != NULL); idx = g_new (int, 1); g_array_append_val (priv->objects, ico); *idx = priv->objects->len - 1; g_hash_table_insert (priv->uid_index_hash, ico->uid, idx); } cal_obj_uid_list_free (uids); } /** * calendar_model_set_cal_client: * @model: A calendar model. * @client: A calendar client interface object. * @type: Type of objects to present. * * Sets the calendar client interface object that a calendar model will monitor. * It also sets the types of objects this model will present to an #ETable. **/ void calendar_model_set_cal_client (CalendarModel *model, CalClient *client, CalObjType type) { CalendarModelPrivate *priv; g_return_if_fail (model != NULL); g_return_if_fail (IS_CALENDAR_MODEL (model)); if (client) g_return_if_fail (IS_CAL_CLIENT (client)); priv = model->priv; if (priv->client == client && priv->type == type) return; if (client) gtk_object_ref (GTK_OBJECT (client)); if (priv->client) { gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), model); gtk_object_unref (GTK_OBJECT (priv->client)); } free_objects (model); priv->client = client; priv->type = type; if (priv->client) { gtk_signal_connect (GTK_OBJECT (priv->client), "obj_updated", GTK_SIGNAL_FUNC (obj_updated_cb), model); gtk_signal_connect (GTK_OBJECT (priv->client), "obj_removed", GTK_SIGNAL_FUNC (obj_removed_cb), model); load_objects (model); } e_table_model_changed (E_TABLE_MODEL (model)); }