Crossfire Client, Branch
R11627
|
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 }