Crossfire Client, Branch  R11627
inventory.c
Go to the documentation of this file.
00001 const char * const rcsid_gtk2_inventory_c =
00002     "$Id: inventory.c 9201 2008-06-01 17:32:45Z anmaster $";
00003 /*
00004     Crossfire client, a client program for the crossfire program.
00005 
00006     Copyright (C) 2005-2007 Mark Wedel & Crossfire Development Team
00007 
00008     This program is free software; you can redistribute it and/or modify
00009     it under the terms of the GNU General Public License as published by
00010     the Free Software Foundation; either version 2 of the License, or
00011     (at your option) any later version.
00012 
00013     This program is distributed in the hope that it will be useful,
00014     but WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016     GNU General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00021 
00022     The author can be reached via e-mail to crossfire@metalforge.org
00023 */
00024 
00030 #ifdef HAVE_CONFIG_H
00031 #  include <config.h>
00032 #endif
00033 
00034 #include <gtk/gtk.h>
00035 #include <glade/glade.h>
00036 
00037 #include "client.h"
00038 
00039 #include "main.h"
00040 #include "image.h"
00041 #include "gtk2proto.h"
00042 
00043 #include "../../pixmaps/all.xpm"
00044 #include "../../pixmaps/coin.xpm"
00045 #include "../../pixmaps/hand.xpm"
00046 #include "../../pixmaps/hand2.xpm"
00047 #include "../../pixmaps/lock.xpm"
00048 #include "../../pixmaps/mag.xpm"
00049 #include "../../pixmaps/nonmag.xpm"
00050 #include "../../pixmaps/skull.xpm"
00051 #include "../../pixmaps/unlock.xpm"
00052 
00053 GtkWidget   *inv_notebook, *treeview_look, *weight_label, *inv_table;
00054 GtkTreeStore    *store_look;
00055 static double  weight_limit;
00056 static GtkTooltips  *inv_table_tooltips;
00057 
00058 /*
00059  * Hopefully, large enough.  Trying to do this with malloc gets more
00060  * complicated because position of elements within the array are important, so
00061  * a simple realloc won't work.
00062  */
00063 #define MAX_INV_COLUMNS 20
00064 #define MAX_INV_ROWS    100
00065 GtkWidget   *inv_table_children[MAX_INV_ROWS][MAX_INV_COLUMNS];
00066 
00067 /* Different styles we recognize */
00068 enum Styles {
00069     Style_Magical=0, Style_Cursed, Style_Unpaid, Style_Locked, Style_Applied, Style_Last
00070 };
00071 
00072 /* The name of these styles in the rc file */
00073 static const char *Style_Names[Style_Last] = {
00074     "inv_magical", "inv_cursed", "inv_unpaid", "inv_locked", "inv_applied"
00075 };
00076 
00077 /* Actual styles as loaded.  May be null if no style found. */
00078 static GtkStyle    *inv_styles[Style_Last];
00079 
00080 /*
00081  * The basic idea of the NoteBook_Info structure is to hold everything we need
00082  * to know about the different inventory notebooks in a module fashion -
00083  * instead of hardcoding values, they can be held in the array.
00084  */
00085 #define NUM_INV_LISTS   10
00086 #define INV_SHOW_ITEM   0x1
00087 #define INV_SHOW_COLOR  0x2
00088 
00089 enum {
00090     INV_TREE,
00091     INV_TABLE
00092 };
00093 
00094 static int num_inv_notebook_pages=0;
00095 
00096 typedef struct {
00097     const char *name;           
00098     const char *tooltip;        
00099     const char *const *xpm;     
00100     int(*show_func) (item *it); 
00104     int     type;               
00109     GtkWidget   *treeview;      
00110     GtkTreeStore    *treestore; 
00111 } Notebook_Info;
00112 
00113 static int show_all(item *it)       { return INV_SHOW_ITEM | INV_SHOW_COLOR; }
00114 static int show_applied(item *it)   { return (it->applied?INV_SHOW_ITEM:0); }
00115 static int show_unapplied(item *it) { return (it->applied?0:INV_SHOW_ITEM); }
00116 static int show_unpaid(item *it)    { return (it->unpaid?INV_SHOW_ITEM:0); }
00117 static int show_cursed(item *it)    { return ((it->cursed | it->damned)?INV_SHOW_ITEM:0); }
00118 static int show_magical(item *it)   { return (it->magical?INV_SHOW_ITEM:0); }
00119 static int show_nonmagical(item *it){ return (it->magical?0:INV_SHOW_ITEM); }
00120 static int show_locked(item *it)    { return (it->locked?(INV_SHOW_ITEM|INV_SHOW_COLOR):0); }
00121 static int show_unlocked(item *it)  { return (it->locked?0:(INV_SHOW_ITEM|INV_SHOW_COLOR)); }
00122 
00123 Notebook_Info   inv_notebooks[NUM_INV_LISTS] = {
00124 {"all", "All Items", all_xpm, show_all, INV_TREE},
00125 {"applied", "Applied Items", hand_xpm, show_applied, INV_TREE},
00126 {"unapplied", "Unapplied Items", hand2_xpm, show_unapplied, INV_TREE},
00127 {"unpaid", "Unpaid items", coin_xpm, show_unpaid, INV_TREE},
00128 {"cursed", "Cursed items", skull_xpm, show_cursed, INV_TREE},
00129 {"magical", "Magical items", mag_xpm, show_magical, INV_TREE},
00130 {"nonmagical", "Nonmagical items", nonmag_xpm, show_nonmagical, INV_TREE},
00131 {"locked", "Inventory locked items", lock_xpm, show_locked, INV_TREE},
00132 {"unlocked", "Inventory unlocked items",unlock_xpm, show_unlocked, INV_TREE},
00133 {"icons", "Quick icon view", NULL, show_all, INV_TABLE}
00134 };
00135 
00136 enum {
00137 LIST_NONE, LIST_ICON, LIST_NAME, LIST_WEIGHT, LIST_OBJECT, LIST_BACKGROUND, LIST_TYPE,
00138 LIST_BASENAME, LIST_FOREGROUND, LIST_FONT, LIST_NUM_COLUMNS
00139 };
00140 
00141 #define ITEM_INVENTORY      0x1
00142 #define ITEM_GROUND         0x2
00143 #define ITEM_IN_CONTAINER   0x4
00144 
00155 static int get_item_env(item *it)
00156 {
00157     if (it->env == cpl.ob) return ITEM_INVENTORY;
00158     if (it->env == cpl.below) return ITEM_GROUND;
00159     if (it->env == NULL) return 0;
00160     return (ITEM_IN_CONTAINER | get_item_env(it->env));
00161 }
00162 
00168 static void list_item_action(GdkEventButton *event, item *tmp)
00169 {
00170     int env;
00171 
00172     /* We need to know where this item is in fact is */
00173     env = get_item_env(tmp);
00174 
00175     /* It'd sure be nice if these weren't hardcoded values for button and
00176      * shift presses.
00177      */
00178     if (event->button == 1) {
00179         if (event->state & GDK_SHIFT_MASK)
00180             toggle_locked(tmp);
00181         else
00182             client_send_examine (tmp->tag);
00183     }
00184     else if (event->button == 2) {
00185         if (event->state & GDK_SHIFT_MASK)
00186             send_mark_obj(tmp);
00187         else
00188             client_send_apply (tmp->tag);
00189     }
00190     else if (event->button == 3) {
00191         if (tmp->locked) {
00192             draw_info ("This item is locked. To drop it, first unlock by shift+leftclicking on it.",
00193                 NDI_BLACK);
00194         } else {
00195             uint32      dest;
00196 
00197             cpl.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(spinbutton_count));
00198             /*
00199              * Figure out where to move the item to.  If it is on the ground,
00200              * it is moving to the players inventory.  If it is in a container,
00201              * it is also moving to players inventory.  If it is in the players
00202              * inventory (not a container) and the player has an open container
00203              * in his inventory, move the object to the container (not ground).
00204              * Otherwise, it is moving to the ground (dest=0).  Have to look at
00205              * the item environment, because what list is no longer accurate.
00206              */
00207             if (env & (ITEM_GROUND | ITEM_IN_CONTAINER))
00208                 dest = cpl.ob->tag;
00209             else if (env == ITEM_INVENTORY && cpl.container &&
00210                      (get_item_env(cpl.container) == ITEM_INVENTORY ||
00211                       get_item_env(cpl.container) == ITEM_GROUND)) {
00212                 dest = cpl.container->tag;
00213             } else
00214                 dest = 0;
00215 
00216             client_send_move (dest, tmp->tag, cpl.count);
00217             gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton_count),0.0);
00218             cpl.count=0;
00219         }
00220     }
00221 }
00222 
00237 gboolean list_selection_func (
00238                       GtkTreeSelection *selection,
00239                       GtkTreeModel     *model,
00240                       GtkTreePath      *path,
00241                       gboolean          path_currently_selected,
00242                       gpointer          userdata)
00243 {
00244     GtkTreeIter iter;
00245     GdkEventButton *event;
00246 
00247     /* Get the current event so we can know if shift is pressed */
00248     event = (GdkEventButton*)gtk_get_current_event();
00249     if (!event) {
00250         LOG(LOG_ERROR,"inventory.c:list_selection_func", "Unable to get event structure\n");
00251         return FALSE;
00252     }
00253 
00254     if (gtk_tree_model_get_iter(model, &iter, path)) {
00255         item *tmp;
00256 
00257         gtk_tree_model_get(model, &iter, LIST_OBJECT, &tmp, -1);
00258 
00259         if (!tmp) {
00260             LOG(LOG_ERROR,"inventory.c:list_selection_func", "Unable to get item structure\n");
00261             return FALSE;
00262         }
00263         list_item_action(event, tmp);
00264     }
00265     /*
00266      * Don't want the row toggled - our code above handles what we need to do,
00267      * so return false.
00268      */
00269     return FALSE;
00270 }
00271 
00281 void
00282 list_row_collapse         (GtkTreeView     *treeview,
00283                                         GtkTreeIter     *iter,
00284                                         GtkTreePath     *path,
00285                                         gpointer         user_data)
00286 {
00287     GtkTreeModel    *model;
00288     item *tmp;
00289 
00290     model = gtk_tree_view_get_model(treeview);
00291 
00292     gtk_tree_model_get(GTK_TREE_MODEL(model), iter, LIST_OBJECT, &tmp, -1);
00293     client_send_apply (tmp->tag);
00294 }
00295 
00300 static void setup_list_columns(GtkWidget *treeview)
00301 {
00302     GtkCellRenderer *renderer;
00303     GtkTreeViewColumn *column;
00304     GtkTreeSelection  *selection;
00305 
00306 #if 0
00307     /*
00308      * This is a hack to hide the expander column.  We do this because access
00309      * via containers need to be handled by the apply/unapply mechanism -
00310      * otherwise, I think it will be confusing - people 'closing' the container
00311      * with the expander arrow and still having things go into/out of the
00312      * container.  Unfortunat
00313      */
00314     renderer = gtk_cell_renderer_text_new ();
00315     column = gtk_tree_view_column_new_with_attributes ("", renderer,
00316                                                       "text", LIST_NONE,
00317                                                       NULL);
00318     gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
00319     gtk_tree_view_column_set_visible(column, FALSE);
00320     gtk_tree_view_set_expander_column(GTK_TREE_VIEW (treeview), column);
00321 #endif
00322 
00323     renderer = gtk_cell_renderer_pixbuf_new ();
00324     /*
00325      * Setting the xalign to 0.0 IMO makes the display better.  Gtk
00326      * automatically resizes the column to make space based on image size,
00327      * however, it isn't really aggressive on shrinking it.  IMO, it looks
00328      * better for the image to always be at the far left - without this
00329      * alignment, the image is centered which IMO doesn't always look good.
00330      */
00331     g_object_set (G_OBJECT (renderer), "xalign", 0.0,
00332                  NULL);
00333     column = gtk_tree_view_column_new_with_attributes ("?", renderer,
00334                                                       "pixbuf", LIST_ICON,
00335                                                       NULL);
00336 
00337 /*  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);*/
00338     gtk_tree_view_column_set_min_width(column, image_size);
00339     gtk_tree_view_column_set_sort_column_id(column, LIST_TYPE);
00340     gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
00341 
00342     renderer = gtk_cell_renderer_text_new ();
00343     column = gtk_tree_view_column_new_with_attributes ("Name", renderer,
00344                                                       "text", LIST_NAME,
00345                                                       NULL);
00346     gtk_tree_view_column_set_expand(column, TRUE);
00347     gtk_tree_view_column_set_sort_column_id(column, LIST_BASENAME);
00348 
00349     gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
00350     gtk_tree_view_column_add_attribute(column, renderer, "background-gdk", LIST_BACKGROUND);
00351     gtk_tree_view_column_add_attribute(column, renderer, "foreground-gdk", LIST_FOREGROUND);
00352     gtk_tree_view_column_add_attribute(column, renderer, "font-desc", LIST_FONT);
00353     gtk_tree_view_set_expander_column(GTK_TREE_VIEW (treeview), column);
00354     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
00355 
00356     renderer = gtk_cell_renderer_text_new ();
00357     column = gtk_tree_view_column_new_with_attributes ("Weight", renderer,
00358                                                       "text", LIST_WEIGHT,
00359                                                       NULL);
00360     /*
00361      * At 50, the title was always truncated on some systems.  64 is the
00362      * minimum on those systems for it to be possible to avoid truncation at
00363      * all.  Truncating the title looks cheesy, especially since heavy items
00364      * (100+) need the width of the field anyway.  If weight pushed off the
00365      * edge is a problem, it would be just better to let the user resize or
00366      * find a way to allow rendering with a smaller font.
00367      */
00368     gtk_tree_view_column_set_min_width(column, 64);
00369     gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
00370 
00371     gtk_tree_view_column_set_sort_column_id(column, LIST_WEIGHT);
00372     gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
00373     gtk_tree_view_column_add_attribute(column, renderer, "background-gdk", LIST_BACKGROUND);
00374     gtk_tree_view_column_add_attribute(column, renderer, "foreground-gdk", LIST_FOREGROUND);
00375     gtk_tree_view_column_add_attribute(column, renderer, "font-desc", LIST_FONT);
00376     /*
00377      * Really, we never really do selections - clicking on an object causes a
00378      * reaction right then.  So grab press before the selection and just negate
00379      * the selection - that's more efficient than unselection the item after it
00380      * was selected.
00381      */
00382     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
00383 
00384     gtk_tree_selection_set_select_function(selection, list_selection_func, NULL, NULL);
00385 }
00386 
00394 void inventory_get_styles(void)
00395 {
00396     int i;
00397     GtkStyle    *tmp_style;
00398     static int has_init=0;
00399 
00400     for (i=0; i < Style_Last; i++) {
00401         if (has_init && inv_styles[i]) g_object_unref(inv_styles[i]);
00402         tmp_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL, Style_Names[i],
00403                                               G_TYPE_NONE);
00404         if (tmp_style) {
00405             inv_styles[i] = g_object_ref(tmp_style);
00406         }
00407         else {
00408             LOG(LOG_INFO, "inventory.c::inventory_get_styles", "Unable to find style for %s",
00409                 Style_Names[i]);
00410             inv_styles[i] = NULL;
00411         }
00412     }
00413     has_init=1;
00414 }
00415 
00421 void inventory_init(GtkWidget *window_root)
00422 {
00423     int i;
00424     GladeXML *xml_tree;
00425 
00426     inventory_get_styles();
00427 
00428     xml_tree = glade_get_widget_tree(GTK_WIDGET(window_root));
00429 
00430     inv_notebook = glade_xml_get_widget(xml_tree,"notebook_inv");
00431     treeview_look = glade_xml_get_widget(xml_tree, "treeview_look");
00432     weight_label = glade_xml_get_widget(xml_tree,"label_inv_weight");
00433     inv_table = glade_xml_get_widget(xml_tree,"inv_table");
00434 
00435     g_signal_connect((gpointer) inv_notebook, "switch_page",
00436         (GCallback) on_notebook_switch_page, NULL);
00437     g_signal_connect((gpointer) inv_table, "expose_event",
00438         (GCallback) on_inv_table_expose_event, NULL);
00439     g_signal_connect((gpointer) treeview_look, "row_collapsed",
00440         (GCallback) list_row_collapse, NULL);
00441 
00442     inv_table_tooltips = gtk_tooltips_new();
00443     gtk_tooltips_enable(inv_table_tooltips);
00444 
00445     memset(inv_table_children, 0, sizeof(GtkWidget *) * MAX_INV_ROWS * MAX_INV_COLUMNS);
00446 
00447     store_look = gtk_tree_store_new (LIST_NUM_COLUMNS,
00448                                 G_TYPE_STRING,
00449                                 G_TYPE_OBJECT,
00450                                 G_TYPE_STRING,
00451                                 G_TYPE_STRING,
00452                                 G_TYPE_POINTER,
00453                                 GDK_TYPE_COLOR,
00454                                 G_TYPE_INT,
00455                                 G_TYPE_STRING,
00456                                 GDK_TYPE_COLOR,
00457                                 PANGO_TYPE_FONT_DESCRIPTION);
00458 
00459     gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_look), GTK_TREE_MODEL(store_look));
00460     setup_list_columns(treeview_look);
00461     /*
00462      * Glade doesn't let us fully realize a treeview widget - we still need to
00463      * to do a bunch of customization just like we do for the look window
00464      * above.  If we have to do all that work, might as well just put it in the
00465      * for loop below vs setting up half realized widgets within glade that we
00466      * then need to finish setting up.  However, that said, we want to be able
00467      * to set up other notebooks within glade for perhaps a true list of just
00468      * icons.  So we presume that any tabs that exist must already be all set
00469      * up.  We prepend our tabs to the existing tab - this makes the position
00470      * of the array of noteboks correspond to actual data in the tabs.
00471      */
00472     for (i=0; i < NUM_INV_LISTS; i++) {
00473         GtkWidget   *swindow, *image;
00474 
00475         if (inv_notebooks[i].type == INV_TREE) {
00476             swindow = gtk_scrolled_window_new(NULL, NULL);
00477             gtk_widget_show(swindow);
00478             gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow),
00479                                            GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
00480             image = gtk_image_new_from_pixbuf(
00481                       gdk_pixbuf_new_from_xpm_data((const char**)inv_notebooks[i].xpm));
00482 
00483             if (inv_notebooks[i].tooltip) {
00484                 GtkWidget *eb;
00485 
00486                 eb=gtk_event_box_new();
00487                 gtk_widget_show(eb);
00488 
00489                 gtk_container_add(GTK_CONTAINER(eb), image);
00490                 gtk_widget_show(image);
00491 
00492                 image=eb;
00493                 gtk_tooltips_set_tip(inv_table_tooltips, image, inv_notebooks[i].tooltip, NULL);
00494             }
00495 
00496             gtk_notebook_insert_page(GTK_NOTEBOOK(inv_notebook), swindow, image, i);
00497 
00498             inv_notebooks[i].treestore = gtk_tree_store_new (LIST_NUM_COLUMNS,
00499                                 G_TYPE_STRING,
00500                                 G_TYPE_OBJECT,
00501                                 G_TYPE_STRING,
00502                                 G_TYPE_STRING,
00503                                 G_TYPE_POINTER,
00504                                 GDK_TYPE_COLOR,
00505                                 G_TYPE_INT,
00506                                 G_TYPE_STRING,
00507                                 GDK_TYPE_COLOR,
00508                                 PANGO_TYPE_FONT_DESCRIPTION);
00509 
00510             inv_notebooks[i].treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
00511                                                     inv_notebooks[i].treestore));
00512 
00513             g_signal_connect ((gpointer)  inv_notebooks[i].treeview, "row_collapsed",
00514                               G_CALLBACK (list_row_collapse), NULL);
00515 
00516             setup_list_columns(inv_notebooks[i].treeview);
00517             gtk_widget_show(inv_notebooks[i].treeview);
00518             gtk_container_add(GTK_CONTAINER(swindow), inv_notebooks[i].treeview);
00519         }
00520     }
00521     num_inv_notebook_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(inv_notebook));
00522 
00523     /* Make sure we are on the first page */
00524     gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), 0);
00525 
00526     /* If all the data is set up properly, these should match */
00527     if (num_inv_notebook_pages != NUM_INV_LISTS) {
00528         LOG(LOG_ERROR,"inventory.c:inventory_init",
00529             "num_inv_notebook_pages (%d) does not match NUM_INV_LISTS(%d)\n",
00530             num_inv_notebook_pages, NUM_INV_LISTS);
00531     }
00532 
00533 }
00534 
00539 void set_show_icon (const char *s)
00540 {
00541 }
00542 
00547 void set_show_weight (const char *s)
00548 {
00549 }
00550 
00559 void close_container(item *op)
00560 {
00561     draw_lists();
00562 }
00563 
00568 void open_container (item *op)
00569 {
00570     draw_lists();
00571 }
00572 
00577 void command_show (const char *params)
00578 {
00579     if(!params)  {
00580         /*
00581          * Shouldn't need to get current page, but next_page call is not
00582          * wrapping like the docs claim it should.
00583          */
00584         if (gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook))==num_inv_notebook_pages)
00585             gtk_notebook_set_page(GTK_NOTEBOOK(inv_notebook), 0);
00586         else
00587             gtk_notebook_next_page(GTK_NOTEBOOK(inv_notebook));
00588     } else {
00589         int i;
00590         char buf[MAX_BUF];
00591 
00592         for (i=0; i < NUM_INV_LISTS; i++) {
00593             if (!strncmp(params, inv_notebooks[i].name, strlen(params))) {
00594                 gtk_notebook_set_page(GTK_NOTEBOOK(inv_notebook), i);
00595                 return;
00596             }
00597         }
00598         snprintf(buf, sizeof(buf), "Unknown notebook page %s\n", params);
00599         draw_info(buf, NDI_RED);
00600     }
00601 }
00602 
00609 void set_weight_limit (uint32 wlim)
00610 {
00611     weight_limit = wlim/ 1000.0;
00612 }
00613 
00619 static GtkStyle *get_row_style(item *it)
00620 {
00621     int style;
00622 
00623     /* Note that this ordering is documented in the sample rc file.
00624      * it would be nice if this precedence could be more easily
00625      * setable by the end user.
00626      */
00627     if (it->unpaid) style=Style_Unpaid;
00628     else if (it->cursed || it->damned) style=Style_Cursed;
00629     else if (it->magical) style = Style_Magical;
00630     else if (it->applied) style = Style_Applied;
00631     else if (it->locked) style = Style_Locked;
00632     else return NULL;   /* No matching style */
00633 
00634     return inv_styles[style];
00635 }
00636 
00637 /***************************************************************************
00638  * Below are the actual guts for drawing the inventory and look windows.
00639  * Some quick notes:
00640  * 1) The gtk2 widgets (treeview/treemodel) seem noticably slower than the
00641  *    older clist widgets in gtk1.  This is beyond the code below - just
00642  *    scrolling the window, which is all done internally by gtk, is
00643  *    quite slow.  Seems especially bad when using the scrollwheel.
00644  * 2) documentation suggests the detaching the treemodel and re-attaching
00645  *    it after insertions would be faster.  The problem is that this causes
00646  *    loss of positioning for the scrollbar. Eg, you eat a food in the middle
00647  *    of your inventory, and then inventory resets to the top of the inventory.
00648  * 3) it'd probably be much more efficient if the code could know what changes
00649  *    are taking place, instead of rebuilding the tree model each time.  For
00650  *    example, if the only thing that changed is the number of of the object,
00651  *    we can just update the name and weight, and not rebuild the entire list.
00652  *    This may be doable in the code below by getting data from the tree store
00653  *    and comparing it against what we want to show - however, figuring out
00654  *    insertions and removals are more difficult.
00655  */
00656 
00657 void item_event_item_deleting(item * it) {}
00658 void item_event_container_clearing(item * container) {}
00659 void item_event_item_changed(item * it) {}
00660 
00674 static void add_object_to_store(item *it, GtkTreeStore *store,
00675                                 GtkTreeIter *new, GtkTreeIter *parent, int color)
00676 {
00677     char    buf[256], buf1[256];
00678     GdkColor    *foreground=NULL, *background=NULL;
00679     PangoFontDescription    *font=NULL;
00680     GtkStyle    *row_style;
00681 
00682     if(it->weight < 0) {
00683         strcpy (buf," ");
00684     } else {
00685         snprintf(buf, sizeof(buf), "%6.1f" ,it->nrof * it->weight);
00686     }
00687     snprintf(buf1, 255, "%s %s", it->d_name, it->flags);
00688     if (color) {
00689         row_style = get_row_style(it);
00690         if (row_style) {
00691             /*
00692              * Even if the user doesn't define these, we should still get get
00693              * defaults from the system.
00694              */
00695             foreground = &row_style->text[GTK_STATE_NORMAL];
00696             background = &row_style->base[GTK_STATE_NORMAL];
00697             font = row_style->font_desc;
00698         }
00699     }
00700 
00701     gtk_tree_store_append (store, new, parent);  /* Acquire an iterator */
00702     gtk_tree_store_set (store, new,
00703                 LIST_ICON, (GdkPixbuf*)pixmaps[it->face]->icon_image,
00704                 LIST_NAME, buf1,
00705                 LIST_WEIGHT, buf,
00706                 LIST_BACKGROUND, background,
00707                 LIST_FOREGROUND, foreground,
00708                 LIST_FONT, font,
00709                 LIST_OBJECT, it,
00710                 LIST_TYPE, it->type,
00711                 LIST_BASENAME, it->s_name,
00712                 -1);
00713 }
00714 
00718 void draw_look_list(void)
00719 {
00720     item *tmp;
00721     GtkTreeIter iter;
00722     /*
00723      * List drawing is actually fairly inefficient - we only know globally if
00724      * the objects has changed, but have no idea what specific object has
00725      * changed.  As such, we are forced to basicly redraw the entire list each
00726      * time this is called.
00727      */
00728     gtk_tree_store_clear(store_look);
00729 
00730     for (tmp=cpl.below->inv; tmp; tmp=tmp->next) {
00731         add_object_to_store(tmp, store_look, &iter, NULL, 1);
00732 
00733         if ((cpl.container == tmp) && tmp->open) {
00734             item  *tmp2;
00735             GtkTreeIter iter1;
00736             GtkTreePath *path;
00737 
00738             for (tmp2 = tmp->inv; tmp2; tmp2=tmp2->next) {
00739                 add_object_to_store(tmp2, store_look, &iter1, &iter, 1);
00740             }
00741             path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_look), &iter);
00742             gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_look), path, FALSE);
00743             gtk_tree_path_free (path);
00744         }
00745     }
00746 }
00747 
00754 void draw_inv_list(int tab)
00755 {
00756     item *tmp;
00757     GtkTreeIter iter;
00758     int rowflag;
00759 
00760     /*
00761      * List drawing is actually fairly inefficient - we only know globally if
00762      * the objects has changed, but have no idea what specific object has
00763      * changed.  As such, we are forced to basicly redraw the entire list each
00764      * time this is called.
00765      */
00766     gtk_tree_store_clear(inv_notebooks[tab].treestore);
00767 
00768     for (tmp=cpl.ob->inv; tmp; tmp=tmp->next) {
00769         rowflag = inv_notebooks[tab].show_func(tmp);
00770         if (!(rowflag & INV_SHOW_ITEM)) continue;
00771 
00772         add_object_to_store(tmp, inv_notebooks[tab].treestore, &iter, NULL, rowflag & INV_SHOW_COLOR);
00773 
00774         if ((cpl.container == tmp) && tmp->open) {
00775             item  *tmp2;
00776             GtkTreeIter iter1;
00777             GtkTreePath *path;
00778 
00779             for (tmp2 = tmp->inv; tmp2; tmp2=tmp2->next) {
00780                 /*
00781                  * Wonder if we really want this logic for objects in
00782                  * containers?  my thought is yes - being able to see all
00783                  * cursed objects in the container could be quite useful.
00784                  * Unfortunately, that doesn't quite work as intended, because
00785                  * we will only get here if the container object is being
00786                  * displayed.  Since container objects can't be cursed, can't
00787                  * use that as a filter.
00788                  */
00789                 /*
00790                 rowflag = inv_notebooks[tab].show_func(tmp2);
00791                 */
00792                 if (!(rowflag & INV_SHOW_ITEM)) continue;
00793                 add_object_to_store(tmp2, inv_notebooks[tab].treestore, &iter1, &iter,
00794                                     rowflag & INV_SHOW_COLOR);
00795             }
00796             path = gtk_tree_model_get_path(GTK_TREE_MODEL(inv_notebooks[tab].treestore), &iter);
00797             gtk_tree_view_expand_row(GTK_TREE_VIEW(inv_notebooks[tab].treeview), path, FALSE);
00798             gtk_tree_path_free (path);
00799         }
00800     }
00801 }
00802 
00810 gboolean
00811 drawingarea_inventory_table_button_press_event  (GtkWidget       *widget,
00812                                         GdkEventButton  *event,
00813                                         gpointer         user_data)
00814 {
00815     list_item_action(event, (item*)user_data);
00816     return TRUE;
00817 }
00818 
00826 gboolean
00827 drawingarea_inventory_table_expose_event        (GtkWidget       *widget,
00828                                         GdkEventExpose  *event,
00829                                         gpointer         user_data)
00830 {
00831     item *tmp;
00832 
00833     tmp = (item*)user_data;
00834 
00835     gdk_window_clear(widget->window);
00836     /*
00837      * Can get cases when switching tabs that we get an expose event before the
00838      * list is updated - if so, don't draw stuff we don't have faces for.
00839      */
00840     if (tmp->face)
00841         gdk_draw_pixbuf(widget->window, NULL,
00842                         (GdkPixbuf*)pixmaps[tmp->face]->icon_image,
00843                         0, 0, 0, 0, image_size, image_size, GDK_RGB_DITHER_NONE, 0, 0);
00844     return TRUE;
00845 }
00846 
00847 #define INVHELPTEXT "Left click examines the object.  Middle click applies \
00848 the object. Right click drops the object.  Shift left click locks/unlocks the \
00849 object.  Shift middle click marks the object"
00850 
00857 void draw_inv_table(int animate)
00858 {
00859     int x, y, rows, columns, num_items, i;
00860     static int max_drawn=0;
00861     item *tmp;
00862     char buf[256];
00863     gulong handler;
00864 
00865     num_items=0;
00866     for (tmp=cpl.ob->inv; tmp; tmp=tmp->next)
00867         num_items++;
00868 
00869     columns = inv_table->allocation.width / image_size;
00870     if (columns > MAX_INV_COLUMNS) columns = MAX_INV_COLUMNS;
00871     rows = inv_table->allocation.height / image_size;
00872 
00873     if (num_items > columns * rows) {
00874         rows = num_items / columns;
00875         if (num_items % columns) rows++;
00876     }
00877     if (rows > MAX_INV_ROWS) rows=MAX_INV_ROWS;
00878 
00879     gtk_table_resize(GTK_TABLE(inv_table), rows, columns);
00880 
00881     x=0;
00882     y=0;
00883     for (tmp=cpl.ob->inv; tmp; tmp=tmp->next) {
00884         if (inv_table_children[x][y] == NULL) {
00885             inv_table_children[x][y] = gtk_drawing_area_new();
00886             gtk_drawing_area_size (GTK_DRAWING_AREA(inv_table_children[x][y]),
00887                                    image_size, image_size);
00888 
00889             gtk_table_attach(GTK_TABLE(inv_table), inv_table_children[x][y],
00890                              x, x+1, y, y+1, GTK_FILL, GTK_FILL, 0, 0);
00891         }
00892         if (animate) {
00893             /* This is an object with animations */
00894             if (tmp->animation_id >0 && tmp->anim_speed) {
00895                 tmp->last_anim++;
00896 
00897                 /* Time to change the face for this one */
00898                 if (tmp->last_anim >= tmp->anim_speed) {
00899                     tmp->anim_state++;
00900                     if (tmp->anim_state >= animations[tmp->animation_id].num_animations)
00901                         tmp->anim_state=0;
00902                     tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
00903                     tmp->last_anim=0;
00904 
00905                     gdk_window_clear(inv_table_children[x][y]->window);
00906                     gdk_draw_pixbuf(inv_table_children[x][y]->window, NULL,
00907                         (GdkPixbuf*)pixmaps[tmp->face]->icon_image,
00908                         0, 0, 0, 0, image_size, image_size, GDK_RGB_DITHER_NONE, 0, 0);
00909                 }
00910             }
00911             /* On animation run, so don't do any of the remaining logic */
00912         } else {
00913             /*
00914              * Need to clear out the old signals, since the signals are
00915              * effectively stacked - you can have 6 signal handlers tied to the
00916              * same function.
00917              */
00918             handler = g_signal_handler_find((gpointer)inv_table_children[x][y],
00919                         G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
00920                         G_CALLBACK (drawingarea_inventory_table_button_press_event),
00921                         NULL);
00922 
00923             if (handler)
00924                 g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
00925 
00926             handler = g_signal_handler_find((gpointer)inv_table_children[x][y],
00927                         G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
00928                         G_CALLBACK (drawingarea_inventory_table_expose_event),
00929                         NULL);
00930             if (handler)
00931                 g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
00932             /*
00933              * Not positive precisely what events are needed, but some events
00934              * beyond just the button press are necessary for the tooltips to
00935              * work.
00936              */
00937             gtk_widget_add_events (inv_table_children[x][y], GDK_ALL_EVENTS_MASK);
00938 
00939             g_signal_connect ((gpointer) inv_table_children[x][y], "button_press_event",
00940                 G_CALLBACK (drawingarea_inventory_table_button_press_event),
00941                 tmp);
00942 
00943             g_signal_connect ((gpointer) inv_table_children[x][y], "expose_event",
00944                 G_CALLBACK (drawingarea_inventory_table_expose_event),
00945                 tmp);
00946 
00947             gdk_window_clear(inv_table_children[x][y]->window);
00948             gdk_draw_pixbuf(inv_table_children[x][y]->window, NULL,
00949                         (GdkPixbuf*)pixmaps[tmp->face]->icon_image,
00950                         0, 0, 0, 0, image_size, image_size, GDK_RGB_DITHER_NONE, 0, 0);
00951 
00952             gtk_widget_show(inv_table_children[x][y]);
00953             /*
00954              * Use tooltips to provide additional detail about the icons.
00955              * Looking at the code, the tooltip widget will take care of
00956              * removing the old tooltip, freeing strings, etc.
00957              */
00958             snprintf(buf, 255, "%s %s", tmp->d_name, tmp->flags);
00959             gtk_tooltips_set_tip(inv_table_tooltips, inv_table_children[x][y],
00960                              buf, INVHELPTEXT);
00961         }
00962         x++;
00963         if (x == columns) {
00964             x=0;
00965             y++;
00966         }
00967 
00968     }
00969     /* Don't need to do the logic below if only doing animation run */
00970     if (animate) return;
00971     /*
00972      * Need to disconnect the callback functions cells we did not draw.
00973      * otherwise, we get errors on objects that are drawn.
00974      */
00975     for (i=num_items; i<=max_drawn; i++) {
00976         if (inv_table_children[x][y]) {
00977             gdk_window_clear(inv_table_children[x][y]->window);
00978 
00979             handler = g_signal_handler_find((gpointer)inv_table_children[x][y],
00980                         G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
00981                         G_CALLBACK (drawingarea_inventory_table_button_press_event),
00982                         NULL);
00983 
00984             if (handler)
00985                 g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
00986 
00987             handler = g_signal_handler_find((gpointer)inv_table_children[x][y],
00988                         G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
00989                         G_CALLBACK (drawingarea_inventory_table_expose_event),
00990                         NULL);
00991             if (handler)
00992                 g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
00993 
00994             /* Hide the widget so that the tooltips doesn't show up */
00995             gtk_widget_hide(inv_table_children[x][y]);
00996         }
00997         x++;
00998         if (x == columns) {
00999             x=0;
01000             y++;
01001         }
01002     }
01003     max_drawn = num_items;
01004 
01005     gtk_widget_show(inv_table);
01006 }
01007 
01013 void draw_inv(int tab)
01014 {
01015     char buf[256];
01016 
01017     snprintf(buf, sizeof(buf), "%6.1f/%6.1f", cpl.ob->weight, weight_limit);
01018     gtk_label_set(GTK_LABEL(weight_label), buf);
01019 
01020     if (inv_notebooks[tab].type == INV_TREE)
01021         draw_inv_list(tab);
01022     else if (inv_notebooks[tab].type == INV_TABLE)
01023         draw_inv_table(0);
01024 }
01025 
01029 void draw_lists (void)
01030 {
01031     cpl.below->inv_updated=1;
01032     /*
01033      * There are some extra complications with container handling and timing.
01034      * For example, we draw the window before we get a list of the container,
01035      * and then the container contents are not drawn - this can be handled by
01036      * looking at container->inv_updated.
01037      */
01038     if (cpl.container && cpl.container->inv_updated) {
01039         cpl.container->env->inv_updated = 1;
01040         cpl.container->inv_updated=0;
01041     }
01042     if (cpl.ob->inv_updated) {
01043         draw_inv(gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook)));
01044         cpl.ob->inv_updated=0;
01045     }
01046     if (cpl.below->inv_updated) {
01047         draw_look_list();
01048         cpl.below->inv_updated=0;
01049     }
01050 }
01051 
01064 void
01065 on_notebook_switch_page                (GtkNotebook     *notebook,
01066                                         GtkNotebookPage *page,
01067                                         guint            page_num,
01068                                         gpointer         user_data)
01069 {
01070     int oldpage;
01071 
01072     oldpage = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
01073     if (oldpage != page_num && inv_notebooks[oldpage].type == INV_TREE)
01074         gtk_tree_store_clear(inv_notebooks[oldpage].treestore);
01075     cpl.ob->inv_updated=1;
01076 }
01077 
01085 gboolean
01086 on_inv_table_expose_event              (GtkWidget       *widget,
01087                                         GdkEventExpose  *event,
01088                                         gpointer         user_data)
01089 {
01090     draw_inv_table(0);
01091     return TRUE;
01092 }
01093 
01097 void animate_inventory(void)
01098 {
01099     gboolean valid;
01100     GtkTreeIter iter;
01101     item *tmp;
01102     int page;
01103     GtkTreeStore    *store;
01104     static int inv_tick=0;
01105 
01106     /*
01107      * If global tick is set, then we are getting tick events from server to
01108      * keep in sync, so we don't need the logic below.
01109      */
01110     if (!tick) {
01111         /*
01112          * The gtk client timeout is 12 times faster than that of the server so
01113          * we slow it down here.  If we were really clever, we'd find what the
01114          * timeout on the server actually is, and do gettimeofday calls here to
01115          * remain very closely in sync.
01116          */
01117         inv_tick++;
01118         if (inv_tick < 12) return;
01119         inv_tick=0;
01120     }
01121 
01122     page = gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook));
01123 
01124     /* Still need to do logic for the table view. */
01125     if (inv_notebooks[page].type == INV_TABLE) {
01126         draw_inv_table(1);
01127         return;
01128     }
01129 
01130     store = inv_notebooks[page].treestore;
01131 
01132     /* Get the first iter in the list */
01133     valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(store), &iter);
01134 
01135     while (valid) {
01136         gtk_tree_model_get (GTK_TREE_MODEL(store), &iter,
01137                           LIST_OBJECT, &tmp,
01138                           -1);
01139 
01140         /* This is an object with animations */
01141         if (tmp->animation_id >0 && tmp->anim_speed) {
01142             tmp->last_anim++;
01143 
01144             /* Time to change the face for this one */
01145             if (tmp->last_anim >= tmp->anim_speed) {
01146                 tmp->anim_state++;
01147                 if (tmp->anim_state >= animations[tmp->animation_id].num_animations)
01148                     tmp->anim_state=0;
01149                 tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
01150                 tmp->last_anim=0;
01151 
01152                 /* Update image in the tree store */
01153                 gtk_tree_store_set(store, &iter,
01154                                    LIST_ICON, (GdkPixbuf*)pixmaps[tmp->face]->icon_image,
01155                                    -1);
01156             }
01157         }
01158         valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(store), &iter);
01159     }
01160 }
01161 
01165 void animate_look(void)
01166 {
01167     gboolean valid;
01168     GtkTreeIter iter;
01169     item *tmp;
01170     static int inv_tick=0;
01171 
01172     /*
01173      * If global tick is set, then we are getting tick events from server to
01174      * keep in sync, so we don't need the logic below.
01175      */
01176     if (!tick) {
01177         /*
01178          * The gtk client timeout is 12 times faster than that of the server so
01179          * we slow it down here.  If we were really clever, we'd find what the
01180          * timeout on the server actually is, and do gettimeofday calls here to
01181          * remain very closely in sync.
01182          */
01183         inv_tick++;
01184         if (inv_tick < 12) return;
01185         inv_tick=0;
01186     }
01187 
01188     /* Get the first iter in the list */
01189     valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL(store_look), &iter);
01190 
01191     while (valid) {
01192         gtk_tree_model_get (GTK_TREE_MODEL(store_look), &iter,
01193                           LIST_OBJECT, &tmp,
01194                           -1);
01195 
01196         /* This is an object with animations */
01197         if (tmp->animation_id >0 && tmp->anim_speed) {
01198             tmp->last_anim++;
01199 
01200             /* Time to change the face for this one */
01201             if (tmp->last_anim >= tmp->anim_speed) {
01202                 tmp->anim_state++;
01203                 if (tmp->anim_state >= animations[tmp->animation_id].num_animations)
01204                     tmp->anim_state=0;
01205                 tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
01206                 tmp->last_anim=0;
01207 
01208                 /* Update image in the tree store */
01209                 gtk_tree_store_set(store_look, &iter,
01210                                    LIST_ICON, (GdkPixbuf*)pixmaps[tmp->face]->icon_image,
01211                                    -1);
01212             }
01213         }
01214         valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(store_look), &iter);
01215     }
01216 }
01217 
01222 void inventory_tick(void)
01223 {
01224     animate_inventory();
01225     animate_look();
01226 }