Utility Functions of the Application

only for RuBoard - do not distribute or recompile

Utility Functions of the Application

In this section, you will construct the functions that will do the majority of the work for your application. You will construct these functions to make them as independent of any user interface as possible. This not only makes them easier to test during bug-tracking activities (What is causing the bug: MySQL, the text box, or something else?), but it forces a clarity of thought on the functions by forcing you to ask What is this function really trying to do, what minimal inputs are needed, and what minimal outputs are required? For example, when you re selecting a value (a customer number in this case), the input is the customer number, and for the output you would prefer to have a record rather than a number of fields. So if you can build a function that will query your database and return a single database record, you have a better function than one that returns multiple values.

Creating sesi_utils.c

First, create a file called sesi_utils.c . This will be the file that contains the utility functions for the application. Refer to Listing 1.1 in Chapter 1, MySQL for Access and SQL Server Developers and DBAs, if necessary. This section is going to present the file sesi_utils.c as a series of listings instead of one large listing so that the individual functions can be covered with a little more detail. It is important that all these utility functions reside in a single file: sesi_utils.c (and its companion, sesi_utils.h ). Also remember that you can get all the files from this book at the book s companion Web site.

First, include the necessary files. Referring to Listing 1.1 (in Chapter 1), stdio.h and mysql.h were included, but only mysql.h needs to be included here. stdio.h was for the printf functions needed in Listing 1.1, but you will be using GLib functions in your application. gtk.h also needs to be declared here so that you have access to its functionality. Therefore, your heading should look like the following:

 #include <mysql.h>  #include <gtk/gtk.h> 
connect_to_db() function

The first thing the application must do is connect to the correct MySQL database. Because this application is meant to be opened, used, and closed in short cycles of a few minutes, it will be safe to make this connection to the database a global variable. This is one of those cases in which a global variable makes sense. Otherwise, the developer is faced with one of two choices: to create a non-global variable that gets passed into or out of nearly every function or to initiate a connection to the database for every query and then end that connection after every single operation is completed.

Listing 7.1 is the database connection routine. It has one purpose: to establish a connection to the MySQL database named sesi.

Listing 7.1 is only one of the functions in the physical file sesi_utils.c . It will be much easier to cover each function on an individual basis than to present one long listing and try to cover it as a single topic. At the book s companion Web site, you can get all the code listings.

Listing 7.1 connect_to_db() Function of sesi_utils.c
 void connect_to_db()  {  /* Listing 7.1   *   * This function establishes a connection to database   * "sesi." It establishes a value for the variable   * conx, which is a (global) session connection   * to the database.   *   * 0L is equal to NULL, but it is considered more portable.   * For a Linux-only program, NULL should work fine.   */  g_print("Establishing db connection...\n");     conx = mysql_init ((MYSQL *)0L);     if (conx == 0L)        {  /* What should the software do if this error occurs?   * Obviously, there is no reason to continue with   * the application because it is of no use without a   * MySQL database connection. In a larger application,   * there would be some logging of the error that occurred   * and various other pieces of information that would aid   * in debugging. But for this application, those actions   * weren't specified. So they will be left out.   *   * Therefore, when a fatal error occurs, the software will   * communicate that to the user, and it will exit.   */  g_print("Failure in mysql_init...\n");          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main, "statusbar")),                        1,                        "Database initialization failed,"                        "contact administrator...");  /* The fatal_msgbox() function is defined next.   * Basically, it is used whenever an error occurs that   * makes it useless to continue.   */  fatal_msgbox("Database initialization failed.\n"                       "Contact the system administrator.");        }  /* In the following call, if the database parameter is misspelled   * (in this case, "sesi"), it is important to note that the   * function call will still return OK instead of an error. The reason   * is that this function primarily connects to a server, not a   * specific database. If you can't connect to the database   * for some reason, you won't know until you attempt to query   * the database for some information.   */  conx = mysql_real_connect (conx, "localhost", "root", 0L, "sesi", 0, 0L, 0);     if (conx == 0L)        {       g_print("Failure in mysql_real_connect...\n");  /* The next two function calls are the first examples   * of error handling in this application. In MySQL, these   * are the error handling functions. Obviously, these   * could be very helpful in debugging and error tracing.   * So the software should attempt to report them.   */  g_print("Error Number is %i...\n", mysql_errno(conx));          g_print("Error Description is %s...\n", mysql_error(conx));          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main, "statusbar")),                        1,                        "Database connection failed, "                        "contact administrator...");  /* Again, if the mysql_real_connect call returns an error,   * there is really no point in continuing.   *  fatal_msgbox(g_strconcat("The connection to the database could",                                   " not be established..\n\n",                                   "Error number: ",                                   g_strdup_printf("%d", mysql_errno(conx)),                                   "\nError description: \n",                                   g_strdup_printf("%s", mysql_error(conx)),                                   0L));        }     g_print("Connected to db...\n");  /* Hit the target database with a simple query as a final   * confirmation that the connection to the database is open.   */  if (mysql_query (conx, "select count(*) from tbl_customers") != 0)        {                 fatal_msgbox(g_strconcat("Unable to connect to ",                                           "database SESI.\n\n",                                           "Error Number: ",                                           g_strdup_printf("%d",                                                    mysql_errno(conx)),                                           "\nError Description: ",                                           g_strdup_printf("%s",                                                    mysql_error(conx)),                                           0L));                g_print("Failure to connect to the correct database...\n");        }     else g_print("Connected to database \"sesi\"...\n");  /* Report to the user that everything to this point is okay. */  gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main, "statusbar")), 1,                        "Connected to database SESI, ready for new order...");  } 
fatal_msgbox() Function

In Listing 7.1, notice that in three places the fatal_msgbox() function is called. That function is shown in Listing 7.2.

Listing 7.2 The fatal_msgbox() Function of sesi_utils.c
 void fatal_msgbox(gchar *msg)  {  /* Listing 7.2   *   * This function is called whenever a fatal but trapable application   * error has occurred. Message boxes are a very poor way to   * communciate to the user; however, when an error occurs that should   * cause the application to terminate, it is nice to give the user   * some form of feedback that attempts to tell him why and gives   * him a chance to do something. In this case, basically all the user   * can do is make a note of the problem and report it to the system   * administrator.   * This function uses  the gtk_dialog_new()  call to create a container   * message box into which a label and an OK button are added.   */  GtkWidget *msgbox;     GtkWidget *lbl;     GtkWidget *cmd_ok;     msgbox = gtk_dialog_new();     lbl = gtk_label_new(g_strconcat("A fatal error has occurred.\n\n",                                     msg,"\n\n",                                     "You may want to make a ",                                     "note of this information.\n\n",                                     "The OK button below will terminate ",                                     "this application",                                     0L));     cmd_ok = gtk_button_new_with_label("Ok");     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(msgbox)->vbox),                                lbl, FALSE, FALSE, FALSE);     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(msgbox)->action_area),                                cmd_ok, FALSE, FALSE, FALSE);  /* Note the following two signal connection calls. The second one   * should be very familiar to you by now, but notice that rather than   * setting up a separate callback function in which  gtk_main_quit()  * would be called (as has been done in all examples   * to this point),  gtk_main_quit()  is called directly.   *   * The first, gtk_signal_connect_object(), is used when the function   * you are invoking has a single parameter of type GtkObject. Notice   * that both produce the same effect. The first is needed because   * there is no guarantee that the user will click the handy "OK"   * button that has been created; he might hit the kill-window "X"   * that is in the top right corner of all windows. Without a callback   * on the  delete_event  signal, he would return to the application   * but be unable to do anything.   */  gtk_signal_connect_object(GTK_OBJECT(msgbox),                               "delete_event",                               GTK_SIGNAL_FUNC(gtk_main_quit),                               GTK_OBJECT(msgbox));     gtk_signal_connect(GTK_OBJECT(cmd_ok),                        "clicked",                        GTK_SIGNAL_FUNC(gtk_main_quit),                        0L);     gtk_widget_show_all(msgbox);  } 
connect_to_db() and fatal_msgbox() Functions

After you have created the two functions in Listing 7.1 and 7.2, you see that the header area of file sesi_utils.c has changed, as shown in Listing 7.3

Listing 7.3 The Header of File sesi_utils.c After the Functions connect_to_db() and fatal_msgbox() Are Created
 01 #include <mysql.h>  02 #include <gtk/gtk.h>  03  04 #include "support.h"  05  06 GtkWidget *frm_main;  07  08  /* conx is the connection to the database; it is global,  09  * and all functions in the application can use it to  10  * access database "sesi".  11  */  12  13 MYSQL *conx;  14  15 /********** Function Prototypes **************/  16 void connect_to_db();  17 void fatal_msgbox(gchar *msg);  18  19 /********** Utility Functions ****************/ 

Lines 4 and 6 are required because the function connect_to_db() references the lookup_widget() to place text into the status bar widget on frm_main. Line 13 is the global database connection variable. Lines 15 and 19 were added for readability, and line 17 is the function prototype for fatal_msgbox() .

Next you ll look at the function that will fill combo box cbo_customer_number with the values for the drop-down box. This is one of the first things the application will have to do.

get_customer_numbers() Function

Listing 7.4 shows the get_customer_numbers() function. Notice that it returns a GList object; this is because it is easy to call the gtk_combo_set_popdown_strings() function, sending it a GList object as the second parameter.

Listing 7.4 The get_conx() and get_customer_numbers() Functions of sesi_utils.c
 void get_conx()  {  /* This function refreshes the connection to the database as   * needed. Note its lack of error handling; since that   * was checked in detail when the application started, this   * makes the assumption that things will still be OKnormally.   */  conx = mysql_init ((MYSQL*)0L);  /* In the following function call, "root" is the user who was logged   * in when MySQL was installed via the RPM. You might need to change   * the user name for your system, depending on how it was installed.   */  mysql_real_connect (conx, "localhost", "root", 0L, "sesi", 0, 0L, 0);  }  GList *get_customer_numbers()  {  /* This function retrieves the list of customer numbers from   * the database and fills cbo_customer_numbers on frm_main.   */  GList *list_of_customers = 0L;     MYSQL_RES *result_set;     MYSQL_ROW row;     get_conx();  /* When frm_main opens, it will default to the first item in the   * drop-down list of the combo box.   */  if (mysql_query (conx, "select distinct(num) from tbl_customers") != 0)        {                 fatal_msgbox(g_strconcat("Unable to retrieve list",                                           " of customer numbers..\n\n",                                           "Error Number: ",                                           g_strdup_printf("%d",                                                   mysql_errno(conx)),                                           "\nError Description: ",                                           g_strdup_printf("%s",                                                   mysql_error(conx)),                                           0L));                g_print("Failure to retreive list of customers..\n");        }     else g_print("Retrieved customer numbers from db...\n");  /* Now iterate through the rows, get the values for customer   * number, and add them to cbo_customer_number.   */  result_set = mysql_store_result (conx);     while ((row = mysql_fetch_row (result_set)) != 0L)        { list_of_customers = g_list_append(list_of_customers, row[0]));        }  /* If you put the New customer number at the end of the list, the   * user can use the up and down arrows to search through the customer   * records without crossing the "New" value.. Adding a new customer   * will be a rather rare occurrance, and this way the user has to   * purposefully go to "New" or type it in..   */  list_of_customers = g_list_append(list_of_customers, "New");     return list_of_customers;  } 
fill_customer_info() , clear_frm_main() , and fill_frm_main() Functions

Now that the combo box of customer numbers is filled, the user will normally pick one, in which case that customer s information should be entered into the text and entry widgets on frm_main. To perform that function, Listing 7.5 will fill in the customer data from the database; in addition, the functions for clearing the text and entry widgets on frm_main are listed.

Listing 7.5 The fill_customer_info() , clear_frm_main() , and fill_frm_main() Functions
 void fill_customer_info()  {  /* This function will retrieve a customer record from   * the database and fill in the text boxes on frm_main by   * calling  fill_frm_main()  .   */  GtkCombo    *cbo;     MYSQL_RES   *result_set;     MYSQL_ROW   row;  /* First, connect to the database, and then get the customer   * number from the entry (child) widget of cbo_customer_number.   */  get_conx();     cbo = GTK_COMBO(lookup_widget(frm_main, "cbo_customer_number"));     g_print(gtk_entry_get_text(GTK_ENTRY(cbo->entry)));     g_print("\n");  /* Check to see if a new customer is being added rather than   * querying for an existing customer. If so, clear frm_main   * and call  create_new_customer()  .   */  if (g_strcasecmp(gtk_entry_get_text (GTK_ENTRY(cbo->entry)), "New") == 0)         {             g_print("Creating new customer record...\n");              clear_frm_main();              create_new_customer();              /* Exit this routine after setting up for a               * new customer record. */              return;         }  /* What if cbo_customer_number is blank? Clear the form, because   * there is no customer information associated with a "blank"   * customer number.   */  if (g_strcasecmp(gtk_entry_get_text (GTK_ENTRY(cbo->entry)), "") == 0)         {             g_print("No Data: Blank Customer Record...\n");              clear_frm_main();              return;         }  /* mysql_query() returns a result only indicating whether or not the   * query sent was legal or not. In this case, sending a query   * with a customer number that does not exist is legal, it just   * returns zero rows.   */  if (mysql_query (conx,                      g_strconcat("select * ",                                  "from tbl_customers where num = ",                                  gtk_entry_get_text (GTK_ENTRY(cbo->entry)),                                  0L)) != 0)       {         g_print("mysql_query failure in fill_customer_info()...\n");          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                                      "statusbar")), 1,                        "Error retrieving customer data (illegal query)."                        " Contact administrator..");       }     else       {         g_print("Fetching customer data...\n");          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                                       "statusbar")), 1,                                           "Fetching Customer data...");          result_set = mysql_store_result (conx);          if (mysql_num_rows (result_set) == 0)          {            g_print("Invalid Customer Number...\n");             gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                                           "statusbar")), 1,                                               "Invalid Customer Number...");  /* Clear frm_main to avoid any confusion. */  clear_frm_main();            }          else          {  /* There could also be a check here to make sure not more than   * one row returned, which would indicate that something is   * wrong tbl_customers.num should be the table key.   *   * However, because that key has been specified   * in the database structure (and to keep this routine a bit   * simpler), that check will be skipped.   */  row = mysql_fetch_row (result_set);            g_print("Preparing for widget fill...\n");  /* Fill frm_main with customer data. The parameter being sent   * is the row of data from the sesi database.   */  fill_frm_main(row);            gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                               "statusbar")), 1,                               "Customer data filled in...");          }       }     return;  }  void clear_frm_main()  {  /* This function clears all the text boxes on frm_main that   * display customer information.   */  g_print("Clearing customer information...\n");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                              "entry_customer_name")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_ship_to_addr1")), ""));      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_ship_to_addr2")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_ship_to_city")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_ship_to_st")), "');      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_ship_to_zip")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_bill_to_addr1")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_bill_to_addr2")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_bill_to_city")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_bill_to_st")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                                               "entry_bill_to_zip")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main, "entry_first")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main, "entry_last")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main, "entry_title")), "");      gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main, "entry_phone")), "");  /* Delete the text from the customer comments   * text box. The order comments box should not   * change no matter which customer is displayed   * because the order comments go with the current   * instantiation of this application.   */  gtk_text_set_point(GTK_TEXT(lookup_widget(frm_main,                                        "txt_customer_comments")), 0);      g_print("current insertion point is %i\n...",              gtk_text_get_point(GTK_TEXT(lookup_widget(frm_main,                                         "txt_customer_comments"))));      g_print("delete returned %i\n",                gtk_text_forward_delete (GTK_TEXT(lookup_widget(frm_main,                                                     "txt_customer_comments")),                            gtk_text_get_length(GTK_TEXT(lookup_widget(frm_main,                                                    "txt_customer_comments")))));  }  void fill_frm_main(MYSQL_ROW in_row)  {  /* This function will fill in the text boxes on frm_main   * that display the customer information.   *   * Clear the form so that the fill operation starts from a known   * state.   */  clear_frm_main();  /* in_row is the parameter to this function. It contains one   * row from tbl_customers, and that information is what is   * to be displayed.   */  gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                          "entry_customer_name")), in_row[1]);      if (in_row[2] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_ship_to_addr1")), in_row[2]);      }      if (in_row[3] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_ship_to_addr2")), in_row[3]);      }      if (in_row[4] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_ship_to_city")), in_row[4]);      }      if (in_row[5] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_ship_to_st")), in_row[5]);      }      if (in_row[6] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_ship_to_zip")), in_row[6]);      }      if (in_row[7] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_bill_to_addr1")), in_row[7]);      }      if (in_row[8] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_bill_to_addr2")), in_row[8]);      }      if (in_row[9] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_bill_to_city")), in_row[9]);      }      if (in_row[10] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_bill_to_st")), in_row[10]);      }      if (in_row[11] != 0L) {        gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                            "entry_bill_to_zip")), in_row[11]);      }      g_print("Filling contact information...\n");      if (in_row[12] != 0L) {         gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                             "entry_first")), in_row[12]);      }      if (in_row[13] != 0L) {         gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                             "entry_last")), in_row[13]);      }      if (in_row[14] != 0L) {         gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                             "entry_phone")), in_row[14]);      }      if (in_row[15] != 0L) {         gtk_entry_set_text(GTK_ENTRY(lookup_widget(frm_main,                             "entry_title")), in_row[15]);      }      if (in_row[16] != 0L) {         gtk_text_insert(GTK_TEXT(lookup_widget(frm_main,                                    "txt_customer_comments")),                          0L, 0L, 0L,                          in_row[16], -1);      }  /* Because the data retrieved has not been edited,   * set the need_to_save_edits flag to false and "gray-out"   * cmd_save_edits to give the user a visual cue that the   * data has not changed since it was pulled from the database.   */  need_to_save_edits = FALSE;      gtk_widget_set_sensitive(lookup_widget(frm_main,                                            "cmd_save_edits"), FALSE);  } 

So far, this chapter has not addressed the code for entering a new customer. That code is shown in Listing 7.14

fill_items_ordered() Function

When the user finds the desired customer, he will want to start entering items ordered. So that code is next. First, the speed entry method (which uses the widgets at the top left of frm_items_ordered) will be covered. Then, the slower method will be described using clist_items and cmd_add_down, which is meant to be a more mouse- intensive way of working. However, before any of that, the form has to fill in all the items and be prepared for use. Listing 7.6 does precisely that.

Listing 7.6 fill_items_ordered() Function from sesi_utils.c
 void fill_items_ordered()  {    MYSQL_RES   *result_set;     MYSQL_ROW   db_row;     gchar       *clist_row[3] = {"", "", ""};  /* This function does the initial fill of widgets in   * frm_items_ordered, as needed. It is anticipated that it will   * normally only be called from the realize or show events of   * frm_items_ordered because the list of available items in the   * database is not expected to change while the application is in   * use.   * The database access code will operate in a manner similar to other   * functions previously listed.   */  get_conx();     if (mysql_query (conx, "select * from tbl_items*) != 0)        {  /* If this query is unable to return a list of items,   * what should be done? Theoretically, if the user   * knows the item number, the application should   * be able to query the database for the desired information   * (price). However, it is more likely that something   * has gone wrong with the connection to the database and   * any read operation against the database will fail. Still,   * it should be given the benefit of the doubt   */  g_print("Failure to retreive list of items...\n");  /* instead of a fatal_msgbox() call. If something   * has really gone wrong, the calls to the database to   * get the price of the item should produce the   * fatal_msgbox() call.   */  }     else g_print("Retrieved customer numbers from db...\n");  /* Now iterate through the rows, get the values for customer number,   * and add them to cbo_customer_number.   */  result_set = mysql_store_result (conx);     while ((db_row = mysql_fetch_row (result_set)) != 0L)        {  clist_row[0] = db_row[0];           clist_row[1] = db_row[1];           clist_row[2] = db_row[2];          gtk_clist_append(GTK_CLIST(lookup_widget(frm_items_ordered,                                                   "clist_items")),                           clist_row);      }  } 
speed_add() Function

Next, the normal course of events is that the user will use the widgets in the upper-left corner of frm_items_ordered to type in item numbers, tab to the Add button (cmd_add), and then repeat this cycle for the entire order. That is, when the customer is on the phone, they know what items they want and either the customer has those order numbers ready, or the person using the software knows the order numbers ”at least the item numbers for the most common items. This will be called speed add and it is covered in Listing 7.7

Listing 7.7 Function speed_add() for Quickly Adding Items to the Order When the Item Number Is Known
 void speed_add()  {    MYSQL_RES   *result_set;     MYSQL_ROW   db_row;     gchar       *sql;     gchar       *clist_row[4] = {"", "", "", ""};     gint        int_quantity;     gchar       *str_quantity;     gdouble     dbl_total_price;     gchar       *str_total_price;     gchar       *str_total_price_formatted;     gchar       *str_order_total;     gchar       *str_order_total_formatted;  /* This function will be called whenever the user clicks on cmd_add,   * which is in the upper-left corner of frm_items_ordered. This is   * the "speedy" method of entering items, as opposed to the "slow"   * method in function slow_add (the next function after this one).   * First, get the item number and get that item's price from the   * database.*/  get_conx();     sql = g_strconcat("select * from tbl_items where item = '",                        gtk_editable_get_chars(GTK_EDITABLE(lookup_widget(frm_items_ordered,                                                         "entry_item_number")) , 0, -1), "'", 0L);        g_print("sql is %s\n", sql);      if (mysql_query (conx, sql) != 0)         {          g_print("Failure to retreive item information from mysql_query...\n");         }      else         {            result_set = mysql_store_result (conx);  /* If the program gets to this point, it has issued a correct   * SQL statement against the database; however, the item   * number could be a non-existent item number. As with   * fill_customer_info(), the software needs to check that the   * number of rows is greater than 0.*/  if (mysql_num_rows (result_set) == 0)             {                g_print("Invalid Item Number...\n");                 gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                       "statusbar")), 1,                                    "Invalid Item Number...");  /* If the user gets to this point, they should see that   * no line has been added to the lower CList box. This is   * an assumption that you would not want to make on a   * project any larger than this one, but the way   * this project is defined makes it an acceptable tradeoff.   */  }             else             {                g_print("Retreived item information...\n");  /* Now that the item number has been verified,   * get quantity, do the math,   * and add to clist_items_ordered.   */  db_row = mysql_fetch_row (result_set);  /* The next two calls demonstrate how to get the same   * information from a spinbutton as two different   * data types - int and gchar*.*/  int_quantity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(lookup_widget(frm_items_ordered,                                      "spinbutton_quantity")));                 str_quantity = gtk_editable_get_chars(GTK_EDITABLE(lookup_widget(frm_items_ordered,                                      "spinbutton_quantity")), 0, -1);                 dbl_total_price = int_quantity * atof(db_row[2]);                 g_print("dbl_total_price is %f\n", dbl_total_price);                 str_total_price = g_strdup_printf("%f", dbl_total_price);                 clist_row[0] = db_row[0];                 clist_row[1] = db_row[1];                 clist_row[2] = str_quantity;                 /* Next, format the output by finding the decimal and then                  * including the next three characters for output as in                  * ".xx".*/                 str_total_price_formatted = g_strndup(str_total_price,                                             strcspn(str_total_price, ".") + 3);                 clist_row[3] == str_total_price_formatted;                 gtk_clist_append(GTK_CLIST                               (lookup_widget(frm_items_ordered,                                              "clist_items_ordered")),                                  clist_row);                 /* Finally, recalculate the total and fill in that                  * label. It is easier to keep a running tally than to                  * try to access the contents of the CList widget.                  */                 dbl_order_total = dbl_order_total + dbl_total_price;                 str_order_total = g_strdup_printf("%f", dbl_order_total);                 str_order_total_formatted = g_strndup(str_order_total,                                             strcspn(str_order_total, ".") +3);                 gtk_label_set_text(GTK_LABEL(lookup_widget                                                (frm_items_ordered,                                                 "lbl_order_total_numeric")),                                    str_order_total_formatted);              }        }  } 
slow_add() Function

Listing 7.8 is the code for the user that is going to click his way through the order. Here, the user will select an item from clist_items, click cmd_add_down (the Add button between the CList widgets), and repeat that process.

Listing 7.8 Function slow_add() for Adding Items to the Order Using the Mouse
 void slow_add()  {  /* This function is the more mouse-intensive way to add   * ordered items to the list, which tends to make it   * a slower way to add items. It is called when the   * user clicks the "Add" button between the two   * list boxes on frm_items_ordered.   */  GtkCList    *clist_target;     gint        row_target = -1;     gchar       *cell_item_number;     gchar       *cell_item_description;     gchar       *cell_item_price;     gchar       *clist_row[4] == {"", "", "", ""};     gint        int_quantity;     gchar       *str_quantity;     gdouble     dbl_total_price;     gchar       *str_total_price;     gchar       *str_total_price_formatted;     gchar       *str_order_total;     gchar       *str_order_total_formatted;     clist_target = GTK_CLIST(lookup_widget(frm_items_ordered, "clist_items"));  /* The following call gets the row that is selected, not focused.   * The 0 is for the "0th" item in the list of selected rows..   */  row_target = (gint)g_list_nth_data((clist_target)->selection, 0);     g_print("Row to move down is %i...\n", row_target);     if (row_target == -1)        {          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                                          "statusbar")), 1,                              "No Item Selected...");           g_print("No Item selected...\n");           return;        }  /* The next three calls get the information about the   * item selected in CList_items, the list of available   * items that a customer can order. They retrieve the   * item number, the description, and the price for   * use later in the function.   */  gtk_clist_get_text(GTK_CLIST(lookup_widget(frm_items_ordered,                        "clist_items")),                        row_target, 0, &cell_item_number);     gtk_clist_get_text(GTK_CLIST(lookup_widget(frm_items_ordered,                        "clist_items")),                        row_target, 1, &cell_item_description);     gtk_clist_get_text(GTK_CLIST(lookup_widget(frm_items_ordered,                        "clist_items")),                        row_target, 2, &cell_item_price);  /* Spinbutton1 is the spinbutton next to cmd_add_down, between the   * two CList boxes. Forgot to change the name on that one :-   */  int_quantity = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(lookup_widget(frm_items_ordered,                                       "spinbutton1")));     str_quantity = gtk_editable_get_chars(GTK_EDITABLE(lookup_widget(frm_items_ordered,                                       "spinbutton1")), 0, -1);  /* Compute the price by multiplying quantity with price,   * then prepare the CList_row[] array by setting the   * values that will be added to the CList widget of   * items the customer has ordered, clist_items_ordered.   */  dbl_total_price = int_quantity * atof(cell_item_price);     g_print("dbl_total_price is %f\n", dbl_total_price);     str_total_price = g_strdup_printf("%f", dbl_total_price);     clist_row[0] = cell_item_number;     clist_row[1] = cell_item_description;     clist_row[2] = str_quantity;     str_total_price_formatted = g_strndup(str_total_price,                                           strcspn(str_total_price, ".") + 3);  /* The previous function call set formatted the price correctly   * for display; the next sets the last field in the array to  
  * that formatted price. Immediately after that, the clist_row[]   * array is added to clist_items_ordered.   */  clist_row[3] == str_total_price_formatted;     gtk_clist_append(GTK_CLIST                        (lookup_widget(frm_items_ordered,                                       "clist_items_ordered")),                      clist_row);  /* Recalculate the running total. */  dbl_order_total = dbl_order_total + dbl_total_price;     str_order_total = g_strdup_printf("%f", dbl_order_total);     str_order_total_formatted = g_strndup(str_order_total,                                           strcspn(str_order_total, ".") +3);     gtk_label_set_text(GTK_LABEL(lookup_widget                                       (frm_items_ordered,                                        "lbl_order_total_numeric")),                        str_order_total_formatted);  } 
remove_ordered_item() Function

Next follows the code to remove a line item that has been added. Listing 7.9 covers removing an item from clist_items_ordered, which also subtracts the appropriate amount from the order total.

Listing 7.9 Function remove_ordered_item() from sesi_utils.c
 void remove_ordered_item()  {  /* This function removes a line from clist_items_ordered,   * most likely because the order entry clerk made a mistake   * or the customer changed his mind. In either case,   * the item needs to be removed and the order total price   * must be recalculated.   */  GtkCList  *clist_target;     gint      row_target = -1;     gchar     *cell_line_item_price;     clist_target = GTK_CLIST(lookup_widget(frm_items_ordered,                                            "clist_items_ordered"));  /* The following call gets the row that is selected, not focused.   * The 0 is for the "0th" item in the list of selected rows.   */  row_target = (gint)g_list_nth_data((clist_target)->selection, 0);     g_print("Row to delete is %i...\n", row_target);     if (row_target == -1)        {          gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                              "statusbar")), 1,                              "Select an item to remove first...");           g_print("No Item selected from clist_items_ordered...\n");           return;        }  /* else continue with the remove operation */   /* First, get the amount of this line item so that it can be   * subtracted from the total before the line is deleted.   */  gtk_clist_get_text(GTK_CLIST(lookup_widget(frm_items_ordered,                        "clist_items_ordered")),                        row_target, 3, &cell_line_item_price);     dbl_order_total = dbl_order_total - atof(cell_line_item_price);     gtk_label_set_text(GTK_LABEL(lookup_widget                           (frm_items_ordered,                            "lbl_order_total_numeric")),                          g_strdup_printf("%f", dbl_order_total));  /* Finally, remove the line item that is selected. */  gtk_clist_remove(clist_target, row_target);  } 
select_item() Function

The select_item() function is a utility function that is used in several places. It takes a single input parameter, searches clist_items for the input parameter, and then selects and shows the selected row in clist_items. The code is in Listing 7.10.

Listing 7.10 Function select_item() from sesi_utils.c , Which Selects and Shows the Line in clist_items That Corresponds to Its Parameter
 void select_item(gchar *target_item_num)  {  /* This function is required for frm_items_ordered to   * workthe find item widget will search the database for an   * item number and then call this function to select that item in   * clist_items. When given as an input for an item number (the   * only parameter to this function), this function iterates   * through all lines in clist_items and finds the one that   * matches the input parameter.   */  GtkCList    *target_clist;     gint        number_of_rows;     gint        counter;     gchar       *clist_item_number;     g_print("Target item number is: %s...\n", target_item_num);     target_clist = GTK_CLIST(lookup_widget(frm_items_ordered, "clist_items"));  /* First, find out how many rows are in the CList widget.   */  number_of_rows = ((target_clist)->rows);     g_print("number_of_rows is: %i", number_of_rows);  /* Iterate through all the rows searching for the target row. */  for(counter = 0; counter < number_of_rows; counter++)      {          gtk_clist_get_text(GTK_CLIST(lookup_widget(frm_items_ordered,                                           "clist_items")),                    counter, 0, &clist_item_number);           if (g_strcasecmp(clist_item_number, target_item_num) == 0)              {                g_print("Found target line %i in clist_items..\n", counter);                 break;              }           else              {  /* continue searching... */  }      }  /* When you have found the desired line in clist_items, select and move it   * into view in the window.   */  gtk_clist_select_row(target_clist, counter, 0);     gtk_clist_moveto(target_clist, counter, 0, 0, 0);  } 
enter_found_items() Function

If the user needs to search for an item, he will open up frm_find_item. There, the function enter_found_items() (shown in in Listing 7.11) will enable him to search the database for matches on the desired character string (the single parameter). With a list of matching items, enter_found_items() (see Listing 7.11) will populate the CList widget in frm_find_item.The user will then pick from that list a single row to return to frm_items_ordered.

Listing 7.11 Function enter_found_items() from sesi_utils.c ; It Searches the Database for Matching Items and Populates the CList Widget in frm_find_item
 void enter_found_items(gchar *str)  {  /* This function will search the tbl_items table for possible   * matches to the user's text (which is "str", the parameter passed   * into this function). It will display those found records   * in clist_found_items.   *   * First, connect to the database and get a result set of   * the possible rows.   *   * Remember that the search will be on a string (the   * input parameter), against   * all possible columns that could match a string,   * that is, all non-numeric fields.   */  MYSQL_RES   *result_set;     MYSQL_ROW   db_row;     gchar       *sql;     gchar       *clist_row[3] = {"", "", ""};     gint        counter;     gint        number_of_rows;     g_print("starting create_find_item_clist...\n");     get_conx();  /* In MySQL, the percent character is the wildcard for all   * possible matches.   */  sql = g_strconcat("select * from tbl_items where item like '%",                       str,                       "%' or description like '%",                       str,                       "%'",                       0L);     g_print("sql is : %s\n", sql);     if (mysql_query (conx, sql) != 0)        {         g_print("Failure to retreive find item data from mysql_query...\n");        }     else        {  /* Retrieve the results and clear clist_found_items. */  result_set = mysql_store_result (conx);          db_row = mysql_fetch_row (result_set);  /* Clear the CList widget of all items. */  gtk_clist_clear(GTK_CLIST(lookup_widget(frm_find_item,                         "clist_found_items")));          number_of_rows = mysql_num_rows(result_set);          g_print("number_of_rows is: %i", number_of_rows);  /* Iterate through the result set and add each row to   * clist_found_items.   */  for (counter = 0; counter < number_of_rows; counter++)            {                 clist_row[0] = db_row[0];                  clist_row[1] = db_row[1];                  clist_row[2] = db_row[2];                  gtk_clist_append(GTK_CLIST(lookup_widget                                   (frm_find_item,                                   "clist_found_items")),                                   clist_row);  /* Fetch the next row. */  db_row = mysql_fetch_row (result_set);              }        }     g_print("exiting create_find_item_clist...\n");  } 
select_customer() and enter_found_customers() Functions

Function enter_found_customers() (see Listing 7.12) performs a similar function in frm_find_customer. Function select_customer() (also Listing 7.12) sets cbo_customer_number to a specified customer and refreshes the data displayed in frm_main. Both are called from the callback initiated by the Done button on frm_find_customer.

Listing 7.12 Functions select_customer() and enter_found_customers() from sesi_utils.c
 void select_customer(gchar *target_customer_num)  {  /* Set cbo_customer_number to target_customer_num, the   * parameter passed in, and then call fill_customer_info.   */  GtkCombo *cbo;     cbo = GTK_COMBO(lookup_widget(frm_main, "cbo_customer_number"));     gtk_entry_set_text(GTK_ENTRY(cbo->entry), target_customer_num);     /* Use the existing customer information fill routine. */     fill_customer_info();  }  void enter_found_customers(gchar *str)  {  /* This function searches for matches to str, the parameter   * passed in, in tbl_customers, and then enters those   * records to clist_found_customer.   *   * First, connect to the database and get a result_set of   * the possible rows.   *   * Remember that the search will be on a string, against   * all possible columns that could match a string.   */  MYSQL_RES *result_set;       MYSQL_ROW   db_row;     gchar       *sql;     gchar       *clist_row[17] = {"","","","","",                                   "","","","","",                                   "","","","","",                                   "",""};     gint        counter;     gint        number_of_rows;     g_print("starting enter_found_customer...\n");     get_conx();  /* In MySQL, the percent character is the wildcard for all   * possible matches.   */  sql = g_strconcat("select * from tbl_customers where name like '%",                       str,                       "%' or ship_to_addr1 like '%",                       str,                       "%' or ship_to_addr2 like '%",                       str,                       "%' or ship_to_city like '%",                       str,                       "%' or ship_to_state like '%",                       str,                       "%' or ship_to_zip like '%",                       str,                       "%' or bill_to_addr1 like '%",                       str,                       "%' or bill_to_addr2 like '%",                       str,                       "%' or bill_to_city like '%",                       str,                       "%' or bill_to_state like '%",                       str,                       "%' or bill_to_zip like '%",                       str,                       "%' or contact_first like '%",                       str,                       "%' or contact_last like '%",                       str,                       "%' or phone like '%",                       str,                       "%' or title like '%",                       str,                       "%' or comments like '%",                       str,                       "%'",                       0L);     g_print("sql is : %s\n", sql);     if (mysql_query (conx, sql) != 0)        {         g_print("Failure to retreive find item data from mysql_query...\n");        }     else        {  /* The query succeeded, so store the result   * and prepare the CList widget to display the   * results.   */  result_set = mysql_store_result (conx);            db_row = mysql_fetch_row (result_set);  /* Clear the CList widget of all items. */  gtk_clist_clear(GTK_CLIST(lookup_widget(frm_find_customer,                                                    "clist_found_customer")));            number_of_rows = mysql_num_rows(result_set);            g_print("number_of_rows is: %i", number_of_rows);  /* Fill the array, which will in turn fill   * clist_found_customer.   */  for (counter = 0; counter < number_of_rows; counter++)              {                   clist_row[0] = db_row[0];                    clist_row[1] = db_row[1];                    clist_row[2] = db_row[2];                    clist_row[3] = db_row[3];                    clist_row[4] = db_row[4];                    clist_row[5] = db_row[5];                    clist_row[6] = db_row[6];                    clist_row[7] = db_row[7];                    clist_row[8] = db_row[8];                    clist_row[9] = db_row[9];                    clist_row[10] = db_row[10];                    clist_row[11] = db_row[11];                    clist_row[12] = db_row[12];                    clist_row[13] = db_row[13];                    clist_row[14] = db_row[14];                    clist_row[15] = db_row[15];                    clist_row[16] = db_row[16];                    clist_row[17] = db_row[17];  /* Finally, append the row to clist_found_customer. */  gtk_clist_append(GTK_CLIST(lookup_widget                                   (frm_find_customer,                                    "clist_found_customer")),                                     clist_row);  /* Fetch the next row. */  db_row = mysql_fetch_row (result_set);             }        }  } 
write_order() Function

Function write_order() produces the final result of this application. Its purpose is to create a filename and then write the order to that disk file. In this case (lacking a better option), it writes to the current directory. It is rather long but fairly straightforward; see Listing 7.13

Listing 7.13 Function write_order() from sesi_utils.c . At the End of the Listing are Functions right_pad() and left_pad() , Which are Only Used by write_order() .
 void write_order()  {  /* This function computes an appropriate filename, gathers   * data from the various forms in the application, and writes   * an order to disk. It uses the current directory by default.   */  gchar      *str_now;     time_t     now;     GtkCList   *target_clist;     gint       number_of_line_items;     GtkCombo   *cbo;     gchar      *cust_name_num;     gchar      *file_name;     FILE       *fp;     gchar      *str_ship_to_csz, *str_bill_to_csz;     gint       counter;     gchar      *cell_item_number, *cell_description, *cell_quantity, *cell_price;     gchar      *str_order_total, *str_order_total_formatted;  /* First, some basic error checking.   * Has a customer been selected?   */  cbo = GTK_COMBO(lookup_widget(frm_main, "cbo_customer_number"));     if (g_strcasecmp(gtk_entry_get_text (GTK_ENTRY(cbo->entry)), "New") == 0)         {             g_print("New customer record, not valid for writing an order...\n");              gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                 "statusbar")), 1,                                 "New is not a valid customer number "                                 "for order generation...");              return;         }     if (g_strcasecmp(gtk_entry_get_text (GTK_ENTRY(cbo->entry)), "") == 0)         {             g_print("No customer record, not valid for writing an order...\n");              gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                 "statusbar")), 1,                                 "Customer Number can not be blank...");              return;         }  /* Have items been ordered? */  target_clist = GTK_CLIST(lookup_widget(frm_items_ordered,                              "clist_items_ordered"));     number_of_line_items = ((target_clist)->rows);     if (number_of_line_items == 0)       {         gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                             "statusbar")), 1,                             "No items have been selected for this invoice...");          return;       }  /* When the error checking is done, it is time to generate a   * filename for this order.   *   * First come the customer name and number.   */  cust_name_num = g_strconcat(gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                             "entry_customer_name"))),                                 ".",                                 gtk_entry_get_text(GTK_ENTRY(cbo->entry)),                                 ".",                                 0L);     g_print("cust_name_num is: %s\n", cust_name_num);  /* Next are the date and time of the order.   *   * The g_strstrip() call is necessary because the ctime() call   * returns a CR/LF character at the end of the string. g_strstrip()   * removes all non-printable characters from the start and end   * of the string that it is given as its parameter.   */  time(&now);     str_now = g_strstrip((gchar *) ctime(&now));     g_print("ctime returns: %s\n", str_now);  /* Now you'll put them all together to get the filename of the order.   * Note that the spaces and colons will be replaced by dots.   */  file_name = g_strconcat(cust_name_num, str_now, ".txt", 0L);     g_print("file_name is %s\n", file_name);  /* The  g_strdelimit()  function replaces all occurrences, any   * member of the second parameter with the first. In this   * case, any space, colon, OR comma will be replaced with   * a period.   *   * Note that the second parameter is surrounded by double   * quotes, and the third parameter is surrounded by single quotes.   *   * The idea here is to create a filename that is sufficiently   * descriptive and will not cause problems on the largest   * number of operating systems. This application was   * built with the idea that the output file produced by this   * function would be transferred to another machinemost   * likely an FTP transfer to a Win32 machine.   */  file_name = g_strdelimit(file_name, " ::,", '.');     g_print("file_name is now %s\n", file_name);  /* Now to open a file handle for writing. */  if ((fp = fopen (file_name, "w")) == 0L)        {  /* Could not write to the file for some reason. */  gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                               "statusbar")), 1,                               "Unable to write to file, "                               "contact system administrator...");        }     else        {  /* File handle is open for write operation. */  gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                              "statusbar")), 1,                              "File is open for write operation...");  /* This file will be fixed width, and to be on the safe side,   * it will be assumed that the "page" is 70 columns wide instead   * of the normal 80.   */   /* First, write the header information: date, time, customer name   * and number, and so on.   */  fprintf(fp, "Specialty Electrical Supply, Inc."); fprintf(fp, "\n");           fprintf(fp, "Shipping Order Form");               fprintf(fp, "\n");           fprintf(fp, "================================="); fprintf(fp, "\n");                                                             fprintf(fp, "\n");           fprintf(fp, str_now);              fprintf(fp, "\n");           fprintf(fp, cust_name_num);        fprintf(fp, "\n");                                              fprintf(fp, "\n");  /* Write the addresses to the file in a side-by-side format. */  fprintf(fp, right_pad("Ship to Address", " ", 35));           fprintf(fp, "Bill to Address"); fprintf(fp, "\n");           fprintf(fp, right_pad(gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_ship_to_addr1"))), " ", 35));           fprintf(fp, gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_bill_to_addr1"))));           fprintf(fp, "\n");           fprintf(fp, right_pad(gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_ship_to_addr2"))), " ", 35));           fprintf(fp, gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_bill_to_addr2"))));           fprintf(fp, "\n");  /* Gather the city, state, and ZIP info into one string, and then pad it.   */  str_ship_to_csz = g_strconcat                             (gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_ship_to_city"))), " ",                                 gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_ship_to_st"))), " ",                                 gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_ship_to_zip"))),                                 0L);           str_bill_to_csz = g_strconcat                             (gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_bill_to_city"))), " ",                                 gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_bill_to_st"))), " ",                                 gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_bill_to_zip"))),                                 0L);           fprintf(fp, right_pad(str_ship_to_csz, " ", 35));           fprintf(fp, str_bill_to_csz);           fprintf(fp, "\n");           fprintf(fp, "\n");           fprintf(fp, "Order Detail Information\n");           fprintf(fp, "========================\n");           fprintf(fp, "\n");           fprintf(fp, right_pad("Item Num", "-", 12));           fprintf(fp, right_pad("Item Description", "-", 37));           fprintf(fp, left_pad("Quantity", "-", 8));           fprintf(fp, left_pad("Price", "-", 13));           fprintf(fp, "\n");  /* Iterate through clist_items_ordered and write the   * order information for each line.   */  for (counter = 0; counter < number_of_line_items; counter++)             {                gtk_clist_get_text(target_clist, counter, 0, &cell_item_number);                 gtk_clist_get_text(target_clist, counter, 1, &cell_description);                 gtk_clist_get_text(target_clist, counter, 2, &cell_quantity);                 gtk_clist_get_text(target_clist, counter, 3, &cell_price);                 fprintf(fp, right_pad(cell_item_number, " ", 12));                 fprintf(fp, right_pad(cell_description, " ", 40));                 fprintf(fp, left_pad(cell_quantity, " ", 5));                 fprintf(fp, left_pad(cell_price, " ", 13));                 fprintf(fp, "\n");                 str_order_total = g_strdup_printf("%f", dbl_order_total);                 str_order_total_formatted = g_strndup(str_order_total,                                            strcspn(str_order_total, ".") +3);             }           fprintf(fp, left_pad("=============", " ", 70)); fprintf(fp, "\n");           fprintf(fp, left_pad(str_order_total_formatted, " ", 70));           fprintf(fp, "\n");           fprintf(fp, "Order Comments\n");           fprintf(fp, "==============\n");           fprintf(fp, gtk_editable_get_chars(GTK_EDITABLE(lookup_widget(frm_main,                     "txt_order_comments")), 0, -1));           fprintf(fp, "\n");           fclose(fp);           gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                              "statusbar")), 1,                              "Order file has been created. "                              "Push exit to close...");        }  }  gchar *right_pad(gchar *in_str, gchar *pad_char, gint final_length)  {  /* This function pads characters to the right of in_str, to   * a length of final_string.   */  while (strlen(in_str) < final_length)       {         in_str = g_strconcat(in_str, pad_char, 0L);       }     return in_str;  }    gchar *left_pad(gchar *in_str, gchar *pad_char, gint final_length)  {  /* This function pads characters to the left of in_str, to   * a length of final_string.   */  while (strlen(in_str) < final_length)       {         in_str = g_strconcat(pad_char, in_str, 0L);       }     return in_str;  } 
update_database() function

The update_database() function writes changes to the database. It takes the don t force it, just get a bigger hammer approach: It overwrites all available fields in the record based on the table key (num, the customer number). See Listing 7.14.

Listing 7.14 Function update_database() , Which Writes Updates to tbl_customers
 void update_database()  {  /* This routine will update the sesi database when changes to a   * customer record have been made.   */  gchar    *sql;     GtkCombo *cbo;  /* Update  tbl_customers  ; don't try to figure out which text   * box was edited, just update all fields.   */  get_conx();     cbo = GTK_COMBO(lookup_widget(frm_main, "cbo_customer_number"));     sql = g_strconcat("update tbl_customers set ",                       "name = '",  gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                     "entry_customer_name"))), "', ",                       "ship_to_addr1 = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_ship_to_addr1"))), "', ",                       "ship_to_addr2 = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_ship_to_addr2"))), "', ",                       "ship_to_city = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_ship_to_city"))), "', ",                       "ship_to_state = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_ship_to_st"))), "', ",                       "ship_to_zip = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_ship_to_zip"))), "', ",                       "bill_to_addr1 = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_bill_to_addr1"))), "', ",                       "bill_to_addr2 = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_bill_to_addr2"))), "', ",                       "bill_to_city = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_bill_to_city"))), "', ",                       "bill_to_state = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_bill_to_st"))), "', ",                       "bill_to_zip = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_bill_to_zip"))), "', ",                       "contact_first = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_first"))), "', ",                       "contact_last = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_last"))), "', ",                       "phone = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_phone"))), "', ",                       "title = '", gtk_entry_get_text(GTK_ENTRY(lookup_widget(frm_main,                                                 "entry_title"))), "', ",                       "comments = '", gtk_editable_get_chars(GTK_EDITABLE(lookup_widget(frm_main,                                                 "txt_customer_comments")),                                                 0, -1), "' ",                       "where num = ", gtk_entry_get_text(GTK_ENTRY(cbo->entry)),                       0L);  /* Finally, check for the success or failure of the update statment. */  if (mysql_query (conx, sql) != 0)     {           g_print("Failure to update customer record,"                    " contact administrator....\n");            gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                                                           "statusbar")), 1,                    "Failure to update customer record, contact administrator.");        }     else        {        g_print("Customer record has been updated...\n");           gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                              "statusbar")), 1,                              "Customer record has been updated...");        }     g_print("sql is %s\n", sql);  } 
create_new_customer() Function

At the end of sesi_utils.c is the create_new_customer() function. The little piece of magic it does is to insert a new record into the sesi database, inserting into the name field. Recall that only the num and name fields are set to NOT NULL. By inserting a new record with only a name, the autonumber feature of tbl_customers sets the num field of the newly created record to max(num) +1. create_new_customer() (shown in Listing 7.15) takes advantage of this behavior by creating the record and then selecting the maximum number from the database, which should be the newly created record (if that operation succeeded). It then sets frm_main to show the newly created record. Note that the user can either select New from cbo_customer_number or type it in. Either way, cbo_customer_number changes to reflect the new customer number.

Listing 7.15 The create_new_customer() Code from sesi_utils.c
 void create_new_customer()  {  /* This routine creates a new customer. It does this by inserting a   * record where only the "num" and "name" fields are entered. The   * autonumbering feature of MySQL automatically creates a new   * customer number in the "num" field. The routine then returns the   * maximum "num" from tbl__customers, which has to be the customer   * just created.   * With this customer number, frm_main is set to show the newly   * created customer, which will have blank text and entry boxes.   * The user then needs to enter the customer information, such as   * phone, address, and so on.   */  gchar       *sql;     MYSQL_RES   *result_set;     MYSQL_ROW   db_row;     get_conx();     sql = "insert into tbl_customers (name) values ('new customer')";  /* Send the query against the database. */  if (mysql_query (conx, sql) != 0)        {         g_print("Failure to create new customer record...\n");            return;        }     else        {           g_print("New customer record created...\n");            gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                               "statusbar")), 1,                               "New customer record has been created...");        }  /* Refresh the combo box of customer numbers. */  gtk_combo_set_popdown_strings(GTK_COMBO(lookup_widget(frm_main,                                               "cbo_customer_number")),                                  get_customer_numbers());  /* Get the max(num) from tbl_customers, which should be the record   * just created.   */  sql = "select max(num) from tbl_customers";     if (mysql_query (conx, sql) != 0)        {         g_print("Failure to retrieve newly created customer record...\n");            return;        }     else        {           g_print("New customer record created...\n");            result_set = mysql_store_result (conx);            db_row = mysql_fetch_row (result_set);  /* Next set cbo_customer_number to the just-retrieved   * customer number, which then displays all the fields   * for that customer. In this case, those text and   * entry boxes will be blank, all except for "name."   */  select_customer(db_row[0]);            gtk_statusbar_push(GTK_STATUSBAR(lookup_widget(frm_main,                               "statusbar")), 1,                               "New customer record has been "                               "created and retrieved, ready for edit...");        }  } 

Finishing sesi_utils.c

To finish up with sesi_utils.c ,you ll start where you began . Listing 7.16 is the header section of sesi_utils.c after the code is finished.

Listing 7.16 Final Header Configuration for sesi_utils.c
 #include <mysql.h>  #include <gtk/gtk.h>  /* stdlib.h is needed for  atof  function.   * string.h is needed for the string search functions used   *          when formatting numbers for output.   * time.h   is needed for the write-to-file operation.   * stdio.h  is also needed for the write-to-file operation.   */  #include <stdlib.h>  #include <string.h>  #include <time.h>  #include <stdio.h>  #include "support.h"  /********** Global Variables *****************/  GtkWidget *frm_main;  GtkWidget *frm_items_ordered;  GtkWidget *frm_find_item;  GtkWidget *frm_find_customer;  /* conx is the connection to the database; it is global,   * and all functions in the application can use it to   * access database "sesi".   */  MYSQL      *conx;  /* dbl_order_total is a global variable that   * can be calculated and accessed without   * converting to and from gchar/gfloat/gdouble   * and so on.   */  gdouble    dbl_order_total = 0;  /* The variable need_to_save_edits tells whether   * changes have been made to any of the text   * widgets that display database fields from   * tbl_customers. Because it needs to be checked   * from many different places and at different times,   * making it global saves time and effort.   */  gboolean   need_to_save_edits;  /********** Function Prototypes **************/  void    connect_to_db();  void    fatal_msgbox(gchar *msg);  void    get_conx();  GList   *get_customer_numbers();  void    fill_customer_info();  void    clear_frm_main();  void    fill_frm_main(MYSQL_ROW in_row);  void    speed_add();  void    slow_add();  void    remove_ordered_item();  void    select_item(gchar *target_item_num);  void    enter_found_items(gchar *str);  void    select_customer(gchar *target_customer_num);  void    enter_found_customers(gchar *str);  void    write_order();  gchar   *right_pad(gchar *in_str, gchar *pad_char, gint final_length);  gchar   *left_pad(gchar *in_str, gchar *pad_char, gint final_length);  void    create_new_customer(); 
only for RuBoard - do not distribute or recompile


MySQL Building User Interfaces
MySQL: Building User Interfaces (Landmark)
ISBN: 073571049X
EAN: 2147483647
Year: 2001
Pages: 119

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net