#include #include #include #include "addressentry.h" /* This enumeration is used to identify the different sort * functions when the user clicks on the header of each view * column. */ enum { SORT_LASTNAME, SORT_FIRSTNAME, SORT_BIRTHDAY }; typedef struct AddressBook AddressBook; /* This structure holds information about a single toplevel window. */ struct AddressBook { char *filename; GtkWidget *window; GtkWidget *treeview; GtkWidget *info_vbox; GtkWidget *firstname_entry; GtkWidget *lastname_entry; GtkWidget *month_optionmenu; GtkWidget *day_spinbutton; GtkWidget *save_menuitem; GtkWidget *back_toolbutton; GtkWidget *forward_toolbutton; GtkWidget *delete_toolbutton; GtkWidget *back_menuitem; GtkWidget *forward_menuitem; GtkWidget *delete_menuitem; GtkWidget *check_quit_dialog; GtkTextBuffer *address_textbuffer; GtkListStore *list_store; gboolean in_change; gboolean modified; }; static GList *books; static gboolean address_book_do_save (AddressBook *book, gboolean force_prompt); /* There is no standard GtkTreeModel operation to iterate backwards, * so we implement one ourselves by converting to a GtkTreePath and back. */ gboolean tree_model_iter_prev (GtkTreeModel *model, GtkTreeIter *iter) { GtkTreePath *path; gboolean result; path = gtk_tree_model_get_path (model, iter); result = gtk_tree_path_prev (path); gtk_tree_model_get_iter (model, iter, path); gtk_tree_path_free (path); return result; } /* Helper function to get a GtkTreeIter pointing to the * last row in a flat GtkTreeModel */ gboolean tree_model_last_iter (GtkTreeModel *model, GtkTreeIter *iter) { if (!gtk_tree_model_get_iter_first (model, iter)) return FALSE; while (TRUE) { GtkTreeIter next = *iter; if (gtk_tree_model_iter_next (model, &next)) *iter = next; else break; } return TRUE; } /* Helper function to print a clear error message if there * is a problem looking up a particular widget */ static GtkWidget * get_widget (GladeXML *glade, const char *name) { GtkWidget *widget = glade_xml_get_widget (glade, name); if (!widget) g_error ("Cannot lookup %s\n", name); return widget; } /* Data functions to set cell renderer properties based on the * data in a particular row. */ static void firstname_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { AddressEntry *entry; gtk_tree_model_get (tree_model, iter, 0, &entry, -1); g_object_set (cell, "text", entry->firstname, NULL); address_entry_unref (entry); } static void lastname_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { AddressEntry *entry; gtk_tree_model_get (tree_model, iter, 0, &entry, -1); g_object_set (cell, "text", entry->lastname, NULL); address_entry_unref (entry); } /* This function would return a pixbuf representating * the zodiacal sign for some date. */ static GdkPixbuf * get_sign_pixbuf (int month, int day) { static GdkPixbuf *pixbuf = NULL; if (!pixbuf) { GError *error = NULL; pixbuf = gdk_pixbuf_new_from_file ("unknown-sign.png", &error); if (!pixbuf) { g_printerr ("Error loading sign image: %s\n", error->message); g_error_free (error); return NULL; } } return pixbuf; } static void sign_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { AddressEntry *entry; gtk_tree_model_get (tree_model, iter, 0, &entry, -1); g_object_set (cell, "pixbuf", get_sign_pixbuf (entry->day, entry->month), NULL); address_entry_unref (entry); } static void birthday_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { const char *const months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; AddressEntry *entry; char *text; gtk_tree_model_get (tree_model, iter, 0, &entry, -1); /* Like sprintf(), but allocates a new string */ text = g_strdup_printf ("%s %d", months[entry->month - 1], entry->day); g_object_set (cell, "text", text, NULL); g_free (text); address_entry_unref (entry); } /* Callback function when there is a change in the currently * selected row in our list of adddresses */ static void address_book_selection_changed (GtkTreeSelection *selection, AddressBook *book) { GtkTreeModel *model; AddressEntry *entry; GtkTreeIter iter; gboolean back_sensitive, forward_sensitive; /* Get the current row, if any */ if (gtk_tree_selection_get_selected (selection, &model, &iter)) { GtkTreeIter tmp_iter; gtk_tree_model_get (model, &iter, 0, &entry, -1); tmp_iter = iter; back_sensitive = tree_model_iter_prev (model, &tmp_iter); tmp_iter = iter; forward_sensitive = gtk_tree_model_iter_next (model, &tmp_iter); } else { back_sensitive = FALSE; forward_sensitive = FALSE; entry = NULL; } book->in_change = TRUE; /* Update the editing widgets */ gtk_entry_set_text (GTK_ENTRY (book->firstname_entry), entry ? entry->firstname : ""); gtk_entry_set_text (GTK_ENTRY (book->lastname_entry), entry ? entry->lastname : ""); gtk_option_menu_set_history (GTK_OPTION_MENU (book->month_optionmenu), entry ? entry->month - 1 : 0); gtk_spin_button_set_value (GTK_SPIN_BUTTON (book->day_spinbutton), entry ? entry->day : 1); gtk_text_buffer_set_text (book->address_textbuffer, entry ? entry->address : "", -1); book->in_change = FALSE; /* Update sensitivity of various controls */ gtk_widget_set_sensitive (book->back_toolbutton, back_sensitive); gtk_widget_set_sensitive (book->back_menuitem, back_sensitive); gtk_widget_set_sensitive (book->forward_toolbutton, forward_sensitive); gtk_widget_set_sensitive (book->forward_menuitem, forward_sensitive); gtk_widget_set_sensitive (book->delete_toolbutton, entry != NULL); gtk_widget_set_sensitive (book->delete_menuitem, entry != NULL); gtk_widget_set_sensitive (book->info_vbox, entry != NULL); if (entry) address_entry_unref (entry); } /* Sort functions when the user clicks on various rows */ int compare_lastname (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { AddressEntry *entry_a; AddressEntry *entry_b; int result; gtk_tree_model_get (model, a, 0, &entry_a, -1); gtk_tree_model_get (model, b, 0, &entry_b, -1); result = strcmp (entry_a->lastname, entry_b->lastname); address_entry_unref (entry_a); address_entry_unref (entry_b); return result; } int compare_firstname (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { AddressEntry *entry_a; AddressEntry *entry_b; int result; gtk_tree_model_get (model, a, 0, &entry_a, -1); gtk_tree_model_get (model, b, 0, &entry_b, -1); result = strcmp (entry_a->firstname, entry_b->firstname); address_entry_unref (entry_a); address_entry_unref (entry_b); return result; } int compare_birthday (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { AddressEntry *entry_a; AddressEntry *entry_b; int result; gtk_tree_model_get (model, a, 0, &entry_a, -1); gtk_tree_model_get (model, b, 0, &entry_b, -1); if (entry_a->month < entry_b->month) return -1; else if (entry_a->month == entry_b->month) { if (entry_a->day < entry_b->day) return -1; else if (entry_a->day > entry_b->day) return 0; else /* entry_a->day > entry_b->day */ return 1; } else /* entry_a->month > entry_b->month */ return 1; address_entry_unref (entry_a); address_entry_unref (entry_b); return result; } /* Do initialization on the address list */ static void address_book_setup_treeview (AddressBook *book) { GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkTreeSelection *selection; book->list_store = gtk_list_store_new (1, ADDRESS_TYPE_ENTRY); gtk_tree_view_set_model (GTK_TREE_VIEW (book->treeview), GTK_TREE_MODEL (book->list_store)); /* First view column, last name */ column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, "Last"); gtk_tree_view_column_set_sort_column_id (column, SORT_LASTNAME); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (book->list_store), SORT_LASTNAME, compare_lastname, NULL, NULL); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func (column, renderer, lastname_data_func, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (book->treeview), column); /* Second view column, first name */ column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, "First"); gtk_tree_view_column_set_sort_column_id (column, SORT_FIRSTNAME); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (book->list_store), SORT_FIRSTNAME, compare_lastname, NULL, NULL); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func (column, renderer, firstname_data_func, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (book->treeview), column); /* Last view column, birthday */ column = gtk_tree_view_column_new (); gtk_tree_view_column_set_title (column, "Birthday"); gtk_tree_view_column_set_sort_column_id (column, SORT_BIRTHDAY); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (book->list_store), SORT_BIRTHDAY, compare_birthday, NULL, NULL); renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, renderer, FALSE); gtk_tree_view_column_set_cell_data_func (column, renderer, sign_data_func, NULL, NULL); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func (column, renderer, birthday_data_func, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (book->treeview), column); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); g_signal_connect (selection, "changed", G_CALLBACK (address_book_selection_changed), book); address_book_selection_changed (selection, book); } /* Get a name to display for the current book, e.g., for use * in a dialog. */ static char * address_book_get_display_name (AddressBook *book) { if (book->filename) return g_path_get_basename (book->filename); else return g_strdup ("Untitled"); } /* Updates the title in the titlebar */ static void address_book_update_title (AddressBook *book) { char *display_name = address_book_get_display_name (book); char *title; title = g_strdup_printf ("%s%s - AddresssBook", display_name, book->modified ? " (modified)" : ""); gtk_window_set_title (GTK_WINDOW (book->window), title); g_free (title); g_free (display_name); } static void address_book_modified (AddressBook *book) { if (!book->modified) { book->modified = TRUE; address_book_update_title (book); gtk_widget_set_sensitive (book->save_menuitem, TRUE); } } static void address_book_unmodified (AddressBook *book) { if (book->modified) { book->modified = FALSE; address_book_update_title (book); gtk_widget_set_sensitive (book->save_menuitem, FALSE); } } static void address_book_set_filename (AddressBook *book, const char *filename) { g_free (book->filename); book->filename = g_strdup (filename); address_book_update_title (book); } /* Displays a "do you really want to quit dialog */ static void address_book_check_quit (AddressBook *book) { char *display_name; gint response; /* We never want more than one such dialog at once, so if we already * have one, just bring it to the front. */ if (book->check_quit_dialog) { gtk_window_present (GTK_WINDOW (book->check_quit_dialog)); return; } if (!book->modified) { gtk_widget_destroy (book->window); return; } display_name = address_book_get_display_name (book); /* Create the dialog without any buttons */ book->check_quit_dialog = gtk_message_dialog_new (GTK_WINDOW (book->window), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "Do you want to save the changes you have made to the addressbook \"%s\"?\n" "\n" "Your changes will be lost if you don't save then.", display_name); g_free (display_name); /* Then add buttons ourselves, since we want a custom set */ gtk_dialog_add_buttons (GTK_DIALOG (book->check_quit_dialog), "_Don't Save", GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_YES, NULL); gtk_dialog_set_default_response (GTK_DIALOG (book->check_quit_dialog), GTK_RESPONSE_YES); response = gtk_dialog_run (GTK_DIALOG (book->check_quit_dialog)); gtk_widget_destroy (book->check_quit_dialog); book->check_quit_dialog = NULL; if (response == GTK_RESPONSE_YES) { if (address_book_do_save (book, FALSE)) gtk_widget_destroy (book->window); } else if (response == GTK_RESPONSE_NO) { gtk_widget_destroy (book->window); } /* Do nothing on cancel */ } /* Given a window, find the main toplevel window for it, and the * AddressBook structure associated with that window. */ static AddressBook * address_book_from_widget (GtkWidget *widget) { AddressBook *book; GtkWidget *toplevel; if (GTK_IS_MENU_ITEM (widget)) { GtkWidget *menu_shell = widget->parent; while (menu_shell && !GTK_IS_MENU_BAR (menu_shell)) menu_shell = gtk_menu_get_attach_widget (GTK_MENU (menu_shell))->parent; toplevel = gtk_widget_get_toplevel (menu_shell); } else toplevel = gtk_widget_get_toplevel (widget); book = g_object_get_data (G_OBJECT (toplevel), "addressbook"); g_assert (book != NULL); return book; } /* Callback when the addressbook toplevel window is destroyed; */ static void on_address_book_destroy (GtkWidget *widget, AddressBook *book) { books = g_list_remove (books, book); /* If there are no books left, exit */ if (books == NULL) gtk_main_quit (); g_free (book->filename); g_free (book); } /* Callback when the user clicks on the titlebar close button */ static gboolean on_address_book_delete (GtkWidget *widget, GdkEventAny *event, AddressBook *book) { address_book_check_quit (book); return TRUE; } /* Gets the currently selected addressbook entry from the list, if any. * We report the iter for the row, so we can later call update_iter() * to update the row. */ static AddressEntry * get_selected_entry (AddressBook *book, GtkTreeIter *iter) { AddressEntry *entry; GtkTreeSelection *selection; GtkTreeModel *model; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); if (gtk_tree_selection_get_selected (selection, &model, iter)) gtk_tree_model_get (model, iter, 0, &entry, -1); else entry = NULL; return entry; } /* By setting the same entry back on a row, causes GtkTreeView * to update the display and sorting for that row. */ static void update_iter (AddressBook *book, AddressEntry *entry, GtkTreeIter *iter) { address_book_modified (book); gtk_list_store_set (book->list_store, iter, 0, entry, -1); } /* Callback functions when the editing controls are modified * by the user. */ static void on_firstname_changed (GtkEntry *entry, AddressBook *book) { AddressEntry *address_entry; GtkTreeIter iter; if (book->in_change) return; address_entry = get_selected_entry (book, &iter); if (address_entry) { const char *value = gtk_entry_get_text (entry); address_entry_set_firstname (address_entry, value); update_iter (book, address_entry, &iter); address_entry_unref (address_entry); } } static void on_lastname_changed (GtkEntry *entry, AddressBook *book) { AddressEntry *address_entry; GtkTreeIter iter; if (book->in_change) return; address_entry = get_selected_entry (book, &iter); if (address_entry) { const char *value = gtk_entry_get_text (entry); address_entry_set_lastname (address_entry, value); update_iter (book, address_entry, &iter); address_entry_unref (address_entry); } } static void on_month_changed (GtkOptionMenu *optionmenu, AddressBook *book) { AddressEntry *address_entry; GtkTreeIter iter; if (book->in_change) return; address_entry = get_selected_entry (book, &iter); if (address_entry) { address_entry->month = 1 + gtk_option_menu_get_history (optionmenu); update_iter (book, address_entry, &iter); address_entry_unref (address_entry); } } static void on_day_value_changed (GtkSpinButton *spinbutton, AddressBook *book) { AddressEntry *address_entry; GtkTreeIter iter; if (book->in_change) return; address_entry = get_selected_entry (book, &iter); if (address_entry) { address_entry->day = gtk_spin_button_get_value (spinbutton); update_iter (book, address_entry, &iter); address_entry_unref (address_entry); } } static void on_address_changed (GtkTextBuffer *buffer, AddressBook *book) { AddressEntry *address_entry; GtkTreeIter iter; if (book->in_change) return; address_entry = get_selected_entry (book, &iter); if (address_entry) { GtkTextIter start; GtkTextIter end; char *address; gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); address = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); address_entry_set_address (address_entry, address); g_free (address); update_iter (book, address_entry, &iter); } } static AddressBook * address_book_new (void) { AddressBook *book; GtkWidget *address_textview; GladeXML *glade; book = g_new0 (AddressBook, 1); books = g_list_prepend (books, book); glade = glade_xml_new ("addressbook.glade", "addressbook-window", NULL); book->window = get_widget (glade, "addressbook-window"); g_object_set_data (G_OBJECT (book->window), "addressbook", book); g_signal_connect (book->window, "delete_event", G_CALLBACK (on_address_book_delete), book); g_signal_connect (book->window, "destroy", G_CALLBACK (on_address_book_destroy), book); book->treeview = get_widget (glade, "addressbook-treeview"); book->info_vbox = get_widget (glade, "addressbook-info-vbox"); book->firstname_entry = get_widget (glade, "addressbook-firstname-entry"); g_signal_connect (book->firstname_entry, "changed", G_CALLBACK (on_firstname_changed), book); book->lastname_entry = get_widget (glade, "addressbook-lastname-entry"); g_signal_connect (book->lastname_entry, "changed", G_CALLBACK (on_lastname_changed), book); book->month_optionmenu = get_widget (glade, "addressbook-month-optionmenu"); g_signal_connect (book->month_optionmenu, "changed", G_CALLBACK (on_month_changed), book); book->day_spinbutton = get_widget (glade, "addressbook-day-spinbutton"); g_signal_connect (book->day_spinbutton, "value_changed", G_CALLBACK (on_day_value_changed), book); address_textview = get_widget (glade, "addressbook-address-textview"); book->address_textbuffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (address_textview)); g_signal_connect (book->address_textbuffer, "changed", G_CALLBACK (on_address_changed), book); book->save_menuitem = get_widget (glade, "save-book"); gtk_widget_set_sensitive (book->save_menuitem, FALSE); book->delete_menuitem = get_widget (glade, "delete-entry"); book->back_menuitem = get_widget (glade, "back"); book->forward_menuitem = get_widget (glade, "forward"); book->delete_toolbutton = get_widget (glade, "addressbook-delete-toolbutton"); book->back_toolbutton = get_widget (glade, "addressbook-back-toolbutton"); book->forward_toolbutton = get_widget (glade, "addressbook-forward-toolbutton"); address_book_setup_treeview (book); glade_xml_signal_autoconnect (glade); address_book_update_title (book); book->in_change = FALSE; return book; } static gboolean address_book_save (AddressBook *book, const char *filename, GError **error) { GtkTreeModel *model = GTK_TREE_MODEL (book->list_store); GtkTreeIter iter; GList *entries = NULL; gboolean valid; gboolean result; for (valid = gtk_tree_model_get_iter_first (model, &iter); valid; valid = gtk_tree_model_iter_next (model, &iter)) { AddressEntry *entry; gtk_tree_model_get (model, &iter, 0, &entry, -1); entries = g_list_prepend (entries, entry); } entries = g_list_reverse (entries); result = address_entry_write_file (filename, entries, error); g_list_foreach (entries, (GFunc)address_entry_unref, NULL); g_list_free (entries); if (result) address_book_unmodified (book); return result; } static gboolean address_book_load (AddressBook *book, const char *filename, GError **error) { GList *entries; GList *l; GtkTreeIter iter; if (!address_entry_read_file (filename, &entries, error)) return FALSE; gtk_list_store_clear (book->list_store); for (l = entries; l; l = l->next) { AddressEntry *entry = l->data; gtk_list_store_append (book->list_store, &iter); gtk_list_store_set (book->list_store, &iter, 0, entry, -1); } g_list_foreach (entries, (GFunc)address_entry_unref, NULL); g_list_free (entries); if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (book->list_store), &iter)) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); gtk_tree_selection_select_iter (selection, &iter); } address_book_unmodified (book); return TRUE; } static gboolean address_book_do_save (AddressBook *book, gboolean force_prompt) { gboolean result = FALSE; if (!book->filename || force_prompt) { GError *error = NULL; GtkWidget *filesel; int response; filesel = gtk_file_selection_new ("Save AddressBook As ..."); gtk_window_set_transient_for (GTK_WINDOW (filesel), GTK_WINDOW (book->window)); response = gtk_dialog_run (GTK_DIALOG (filesel)); gtk_widget_hide (filesel); if (response == GTK_RESPONSE_OK) { const char *filename; filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)); if (!filename || g_file_test (filename, G_FILE_TEST_IS_DIR)) { g_warning ("No filename given"); } else { if (!address_book_save (book, filename, &error)) { g_warning ("Could not save addressbook: %s", error->message); g_error_free (error); } else { address_book_set_filename (book, filename); result = TRUE; } } } gtk_widget_destroy (filesel); } else { GError *error = NULL; if(!address_book_save (book, book->filename, &error)) { g_warning ("Could not save addressbook: %s\n", error->message); g_error_free (error); } else result = TRUE; } return result; } /* The following functions are publically exported because they * are used with glade_xml_signal_autoconnect (glade); */ void on_new_book_activate (GtkWidget *widget) { AddressBook *book = address_book_new (); gtk_widget_show (book->window); } void on_open_book_activate (GtkWidget *widget) { AddressBook *book = address_book_from_widget (widget); GError *error = NULL; GtkWidget *filesel; int response; GtkTreeIter tmp_iter; filesel = gtk_file_selection_new ("Save AddresBook ..."); gtk_window_set_transient_for (GTK_WINDOW (filesel), GTK_WINDOW (book->window)); response = gtk_dialog_run (GTK_DIALOG (filesel)); gtk_widget_hide (filesel); if (response == GTK_RESPONSE_OK) { const char *filename; filename = gtk_file_selection_get_filename (GTK_FILE_SELECTION (filesel)); if (!filename || g_file_test (filename, G_FILE_TEST_IS_DIR)) { g_warning ("No filename given"); } else { AddressBook *new_book; if (book->filename || gtk_tree_model_get_iter_first (GTK_TREE_MODEL (book->list_store), &tmp_iter)) new_book = address_book_new (); else new_book = book; if (!address_book_load (new_book, filename, &error)) { g_warning ("Could not load addressbook: %s", error->message); g_error_free (error); if (new_book != book) gtk_widget_destroy (new_book->window); } else { address_book_set_filename (new_book, filename); gtk_widget_show (new_book->window); } } } gtk_widget_destroy (filesel); } void on_save_book_as_activate (GtkWidget *widget) { AddressBook *book = address_book_from_widget (widget); address_book_do_save (book, TRUE); } void on_save_book_activate (GtkWidget *widget) { AddressBook *book = address_book_from_widget (widget); address_book_do_save (book, FALSE); } void on_close_book_activate (GtkWidget *widget) { AddressBook *book = address_book_from_widget (widget); address_book_check_quit (book); } /* Callbacks for menu actions on particular addressbook entries */ void on_new_entry_activate (GtkWidget *widget) { AddressBook *book; AddressEntry *entry; GtkTreeIter iter; GtkTreeSelection *selection; book = address_book_from_widget (widget); entry = address_entry_new (); address_entry_set_firstname (entry, ""); address_entry_set_lastname (entry ,""); gtk_list_store_append (book->list_store, &iter); gtk_list_store_set (book->list_store, &iter, 0, entry, -1); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); gtk_tree_selection_select_iter (selection, &iter); gtk_widget_grab_focus (book->firstname_entry); address_entry_unref (entry); address_book_modified (book); } void on_delete_entry_activate (GtkWidget *widget) { AddressBook *book; GtkTreeIter iter; GtkTreeSelection *selection; book = address_book_from_widget (widget); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); if (gtk_tree_selection_get_selected (selection, NULL, &iter)) { if (gtk_list_store_remove (book->list_store, &iter) || tree_model_last_iter (GTK_TREE_MODEL (book->list_store), &iter)) gtk_tree_selection_select_iter (selection, &iter); address_book_modified (book); } } void on_back_activate (GtkWidget *widget) { AddressBook *book; GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; book = address_book_from_widget (widget); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { if (tree_model_iter_prev (model, &iter)) gtk_tree_selection_select_iter (selection, &iter); } } void on_forward_activate (GtkWidget *widget) { AddressBook *book; GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection *selection; book = address_book_from_widget (widget); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (book->treeview)); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { if (gtk_tree_model_iter_next (model, &iter)) gtk_tree_selection_select_iter (selection, &iter); } } /* Create an initial book, and set the main loop going */ int main (int argc, char **argv) { AddressBook *book; gtk_init (&argc, &argv); book = address_book_new (); gtk_widget_show (book->window); gtk_main (); return 0; }