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