Crossfire Client, Trunk  R20693
inventory.c
Go to the documentation of this file.
1 /*
2  * Crossfire -- cooperative multi-player graphical RPG and adventure game
3  *
4  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
5  * Copyright (c) 1992 Frank Tore Johansen
6  *
7  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
8  * welcome to redistribute it under certain conditions. For details, see the
9  * 'LICENSE' and 'COPYING' files.
10  *
11  * The authors can be reached via e-mail to crossfire-devel@real-time.com
12  */
13 
19 #include "client.h"
20 
21 #include <gtk/gtk.h>
22 
23 #include "image.h"
24 #include "main.h"
25 #include "gtk2proto.h"
26 
27 #include "../../pixmaps/all.xpm"
28 #include "../../pixmaps/coin.xpm"
29 #include "../../pixmaps/hand.xpm"
30 #include "../../pixmaps/hand2.xpm"
31 #include "../../pixmaps/lock.xpm"
32 #include "../../pixmaps/mag.xpm"
33 #include "../../pixmaps/nonmag.xpm"
34 #include "../../pixmaps/skull.xpm"
35 #include "../../pixmaps/unlock.xpm"
36 #include "../../pixmaps/unidentified.xpm"
37 
38 GtkWidget *treeview_look;
39 
41 static const GdkColor applied_color = {0, 50000, 50000, 50000};
42 
43 static GtkTreeStore *store_look;
44 static GtkWidget *encumbrance_current;
45 static GtkWidget *encumbrance_max;
46 static GtkWidget *inv_notebook;
47 static GtkWidget *inv_table;
48 
49 static double weight_limit;
50 
51 /*
52  * Hopefully, large enough. Trying to do this with g_malloc gets more
53  * complicated because position of elements within the array are important, so
54  * a simple g_realloc won't work.
55  */
56 #define MAX_INV_COLUMNS 20
57 #define MAX_INV_ROWS 100
59 
60 /* Different styles we recognize */
61 enum Styles {
63 };
64 
65 /* The name of these styles in the rc file */
66 static const char *Style_Names[Style_Last] = {
67  "inv_magical", "inv_cursed", "inv_unpaid", "inv_locked", "inv_applied"
68 };
69 
70 /* Actual styles as loaded. May be null if no style found. */
71 static GtkStyle *inv_styles[Style_Last];
72 
73 /*
74  * The basic idea of the NoteBook_Info structure is to hold everything we need
75  * to know about the different inventory notebooks in a module fashion -
76  * instead of hardcoding values, they can be held in the array.
77  */
78 #define NUM_INV_LISTS 11
79 #define INV_SHOW_ITEM 0x1
80 #define INV_SHOW_COLOR 0x2
81 
88 };
89 
90 static int num_inv_notebook_pages = 0;
91 
92 typedef struct {
93  const char *name;
94  const char *tooltip;
95  const char *const *xpm;
96  int(*show_func) (item *it);
100  enum display_type type;
101  GtkWidget *treeview;
102  GtkTreeStore *treestore;
103 } Notebook_Info;
104 
105 /* Prototypes for static functions */
106 static gboolean on_inv_table_expose_event(GtkWidget *widget,
107  GdkEventExpose *event, gpointer user_data);
108 
109 static void on_switch_page(GtkNotebook *notebook, gpointer *page,
110  guint page_num, gpointer user_data);
111 
112 static int show_all(item *it) {
113  return INV_SHOW_ITEM | INV_SHOW_COLOR;
114 }
115 
116 static int show_applied(item *it) {
117  return (it->applied ? INV_SHOW_ITEM : 0);
118 }
119 
120 static int show_unapplied(item *it) {
121  return (it->applied ? 0 : INV_SHOW_ITEM);
122 }
123 
124 static int show_unpaid(item *it) {
125  return (it->unpaid ? INV_SHOW_ITEM : 0);
126 }
127 
128 static int show_cursed(item *it) {
129  return ((it->cursed | it->damned) ? INV_SHOW_ITEM : 0);
130 }
131 
132 static int show_magical(item *it) {
133  return (it->magical ? INV_SHOW_ITEM : 0);
134 }
135 
136 static int show_nonmagical(item *it) {
137  return (it->magical ? 0 : INV_SHOW_ITEM);
138 }
139 
140 static int show_locked(item *it) {
141  return (it->locked ? (INV_SHOW_ITEM | INV_SHOW_COLOR) : 0);
142 }
143 
144 static int show_unlocked(item *it) {
145  // Show open containers, even if locked, to make moving items easier.
146  return ((it->locked && !it->open) ? 0 : (INV_SHOW_ITEM | INV_SHOW_COLOR));
147 }
148 
149 static int show_unidentified(item *it) {
150  return ((it->flagsval & F_UNIDENTIFIED) ? INV_SHOW_ITEM : 0);
151 }
152 
154  {"all", "All", all_xpm, show_all, INV_TREE},
155  {"applied", "Applied", hand_xpm, show_applied, INV_TREE},
156  {"unapplied", "Unapplied", hand2_xpm, show_unapplied, INV_TREE},
157  {"unpaid", "Unpaid", coin_xpm, show_unpaid, INV_TREE},
158  {"cursed", "Cursed", skull_xpm, show_cursed, INV_TREE},
159  {"magical", "Magical", mag_xpm, show_magical, INV_TREE},
160  {"nonmagical", "Non-magical", nonmag_xpm, show_nonmagical, INV_TREE},
161  {"locked", "Locked", lock_xpm, show_locked, INV_TREE},
162  {"unlocked", "Unlocked", unlock_xpm, show_unlocked, INV_TREE},
163  {"unidentified", "Unidentified", unidentified_xpm, show_unidentified, INV_TREE},
164  {"icons", "Icon View", NULL, show_all, INV_TABLE}
165 };
166 
174 };
175 
181 enum item_env {
183 };
184 
195 static int get_item_env(item *it) {
196  if (it->env == cpl.ob) {
197  return ITEM_INVENTORY;
198  }
199  if (it->env == cpl.below) {
200  return ITEM_GROUND;
201  }
202  if (it->env == NULL) {
203  return 0;
204  }
205  return (ITEM_IN_CONTAINER | get_item_env(it->env));
206 }
207 
213 static void list_item_action(GdkEventButton *event, item *tmp) {
214  int env;
215 
216  /* We need to know where this item is in fact is */
217  env = get_item_env(tmp);
218 
219  /* It'd sure be nice if these weren't hardcoded values for button and
220  * shift presses.
221  */
222  if (event->button == 1) {
223  if (event->state & GDK_SHIFT_MASK) {
224  toggle_locked(tmp);
225  } else {
226  client_send_examine(tmp->tag);
227  }
228  } else if (event->button == 2) {
229  if (event->state & GDK_SHIFT_MASK) {
230  send_mark_obj(tmp);
231  } else {
232  client_send_apply(tmp->tag);
233  }
234  } else if (event->button == 3) {
235  if (tmp->locked) {
237  "This item is locked. To drop it, first unlock by shift+leftclicking on it.");
238  } else {
239  guint32 dest;
240 
241  cpl.count = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spinbutton_count));
242  /*
243  * Figure out where to move the item to. If it is on the ground,
244  * it is moving to the players inventory. If it is in a container,
245  * it is also moving to players inventory. If it is in the players
246  * inventory (not a container) and the player has an open container
247  * in his inventory, move the object to the container (not ground).
248  * Otherwise, it is moving to the ground (dest=0). Have to look at
249  * the item environment, because what list is no longer accurate.
250  */
251  if (env & (ITEM_GROUND | ITEM_IN_CONTAINER)) {
252  dest = cpl.ob->tag;
253  } else if (env == ITEM_INVENTORY && cpl.container &&
256  dest = cpl.container->tag;
257  } else {
258  dest = 0;
259  }
260 
261  client_send_move(dest, tmp->tag, cpl.count);
262  gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton_count), 0.0);
263  cpl.count = 0;
264  }
265  }
266 }
267 
282 static gboolean list_selection_func(GtkTreeSelection *selection,
283  GtkTreeModel *model, GtkTreePath *path,
284  gboolean path_currently_selected, gpointer userdata) {
285  GtkTreeIter iter;
286  GdkEventButton *event;
287 
288  /* Get the current event so we can know if shift is pressed */
289  event = (GdkEventButton*)gtk_get_current_event();
290  if (!event) {
291  LOG(LOG_ERROR, "inventory.c::list_selection_func", "Unable to get event structure\n");
292  return FALSE;
293  }
294 
295  if (gtk_tree_model_get_iter(model, &iter, path)) {
296  item *tmp;
297 
298  gtk_tree_model_get(model, &iter, LIST_OBJECT, &tmp, -1);
299 
300  if (!tmp) {
301  LOG(LOG_ERROR, "inventory.c::list_selection_func", "Unable to get item structure\n");
302  return FALSE;
303  }
304  list_item_action(event, tmp);
305  }
306  /*
307  * Don't want the row toggled - our code above handles what we need to do,
308  * so return false.
309  */
310  return FALSE;
311 }
312 
322 static void list_row_collapse(GtkTreeView *treeview, GtkTreeIter *iter,
323  GtkTreePath *path, gpointer user_data) {
324  GtkTreeModel *model;
325  item *tmp;
326 
327  model = gtk_tree_view_get_model(treeview);
328 
329  gtk_tree_model_get(GTK_TREE_MODEL(model), iter, LIST_OBJECT, &tmp, -1);
330  client_send_apply(tmp->tag);
331 }
332 
337 static void setup_list_columns(GtkWidget *treeview) {
338  GtkCellRenderer *renderer;
339  GtkTreeViewColumn *column;
340  GtkTreeSelection *selection;
341 
342 #if 0
343  /*
344  * This is a hack to hide the expander column. We do this because access
345  * via containers need to be handled by the apply/unapply mechanism -
346  * otherwise, I think it will be confusing - people 'closing' the container
347  * with the expander arrow and still having things go into/out of the
348  * container. Unfortunat
349  */
350  renderer = gtk_cell_renderer_text_new();
351  column = gtk_tree_view_column_new_with_attributes("", renderer,
352  "text", LIST_NONE,
353  NULL);
354  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
355  gtk_tree_view_column_set_visible(column, FALSE);
356  gtk_tree_view_set_expander_column(GTK_TREE_VIEW(treeview), column);
357 #endif
358 
359  renderer = gtk_cell_renderer_pixbuf_new();
360  /*
361  * Setting the xalign to 0.0 IMO makes the display better. Gtk
362  * automatically resizes the column to make space based on image size,
363  * however, it isn't really aggressive on shrinking it. IMO, it looks
364  * better for the image to always be at the far left - without this
365  * alignment, the image is centered which IMO doesn't always look good.
366  */
367  g_object_set(G_OBJECT(renderer), "xalign", 0.0, NULL);
368  column = gtk_tree_view_column_new_with_attributes("?", renderer,
369  "pixbuf", LIST_ICON, NULL);
370 
371  /* gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);*/
372  gtk_tree_view_column_set_min_width(column, image_size);
373  gtk_tree_view_column_set_sort_column_id(column, LIST_TYPE);
374  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
375 
376  renderer = gtk_cell_renderer_text_new();
377  column = gtk_tree_view_column_new_with_attributes("Name", renderer,
378  "text", LIST_NAME, NULL);
379  gtk_tree_view_column_set_expand(column, TRUE);
380  gtk_tree_view_column_set_sort_column_id(column, LIST_BASENAME);
381 
382  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
383  gtk_tree_view_column_add_attribute(column, renderer, "background-gdk", LIST_BACKGROUND);
384  gtk_tree_view_column_add_attribute(column, renderer, "foreground-gdk", LIST_FOREGROUND);
385  gtk_tree_view_column_add_attribute(column, renderer, "font-desc", LIST_FONT);
386  gtk_tree_view_set_expander_column(GTK_TREE_VIEW(treeview), column);
387  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
388 
389  renderer = gtk_cell_renderer_text_new();
390  column = gtk_tree_view_column_new_with_attributes("Weight", renderer,
391  "text", LIST_WEIGHT, NULL);
392  /*
393  * At 50, the title was always truncated on some systems. 64 is the
394  * minimum on those systems for it to be possible to avoid truncation at
395  * all. Truncating the title looks cheesy, especially since heavy items
396  * (100+) need the width of the field anyway. If weight pushed off the
397  * edge is a problem, it would be just better to let the user resize or
398  * find a way to allow rendering with a smaller font.
399  */
400  gtk_tree_view_column_set_min_width(column, 64);
401  gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
402 
403  gtk_tree_view_column_set_sort_column_id(column, LIST_WEIGHT);
404  gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
405  gtk_tree_view_column_add_attribute(column, renderer, "background-gdk", LIST_BACKGROUND);
406  gtk_tree_view_column_add_attribute(column, renderer, "foreground-gdk", LIST_FOREGROUND);
407  gtk_tree_view_column_add_attribute(column, renderer, "font-desc", LIST_FONT);
408  /*
409  * Really, we never really do selections - clicking on an object causes a
410  * reaction right then. So grab press before the selection and just negate
411  * the selection - that's more efficient than unselection the item after it
412  * was selected.
413  */
414  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
415 
416  gtk_tree_selection_set_select_function(selection, list_selection_func, NULL, NULL);
417 }
418 
427  int i;
428  GtkStyle *tmp_style;
429  static int has_init = 0;
430 
431  for (i = 0; i < Style_Last; i++) {
432  if (has_init && inv_styles[i]) {
433  g_object_unref(inv_styles[i]);
434  }
435  tmp_style = gtk_rc_get_style_by_paths(gtk_settings_get_default(), NULL, Style_Names[i],
436  G_TYPE_NONE);
437  if (tmp_style) {
438  inv_styles[i] = g_object_ref(tmp_style);
439  } else {
440  LOG(LOG_INFO, "inventory.c::inventory_get_styles", "Unable to find style for %s",
441  Style_Names[i]);
442  inv_styles[i] = NULL;
443  }
444  }
445  has_init = 1;
446 }
447 
453 void inventory_init(GtkWidget *window_root) {
454  int i;
455 
456  /*inventory_get_styles();*/
457 
458  inv_notebook = GTK_WIDGET(gtk_builder_get_object(window_xml,
459  "notebook_inv"));
460  treeview_look = GTK_WIDGET(gtk_builder_get_object(window_xml,
461  "treeview_look"));
462  encumbrance_current = GTK_WIDGET(gtk_builder_get_object(window_xml,
463  "label_stat_encumbrance_current"));
464  encumbrance_max = GTK_WIDGET(gtk_builder_get_object(window_xml,
465  "label_stat_encumbrance_max"));
466  inv_table = GTK_WIDGET(gtk_builder_get_object(window_xml,
467  "inv_table"));
468 
469  g_signal_connect((gpointer)inv_notebook, "switch_page",
470  (GCallback)on_switch_page, NULL);
471  g_signal_connect((gpointer) inv_table, "expose_event",
472  (GCallback) on_inv_table_expose_event, NULL);
473  g_signal_connect((gpointer) treeview_look, "row_collapsed",
474  (GCallback) list_row_collapse, NULL);
475 
476  memset(inv_table_children, 0, sizeof (GtkWidget *) * MAX_INV_ROWS * MAX_INV_COLUMNS);
477 
478  store_look = gtk_tree_store_new(LIST_NUM_COLUMNS,
479  G_TYPE_STRING,
480  G_TYPE_OBJECT,
481  G_TYPE_STRING,
482  G_TYPE_STRING,
483  G_TYPE_POINTER,
484  GDK_TYPE_COLOR,
485  G_TYPE_INT,
486  G_TYPE_STRING,
487  GDK_TYPE_COLOR,
488  PANGO_TYPE_FONT_DESCRIPTION);
489 
490  gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_look), GTK_TREE_MODEL(store_look));
491  setup_list_columns(treeview_look);
492  /*
493  * Glade doesn't let us fully realize a treeview widget - we still need to
494  * to do a bunch of customization just like we do for the look window
495  * above. If we have to do all that work, might as well just put it in the
496  * for loop below vs setting up half realized widgets within layout that we
497  * then need to finish setting up. However, that said, we want to be able
498  * to set up other notebooks within layout for perhaps a true list of just
499  * icons. So we presume that any tabs that exist must already be all set
500  * up. We prepend our tabs to the existing tab - this makes the position
501  * of the array of noteboks correspond to actual data in the tabs.
502  */
503  for (i = 0; i < NUM_INV_LISTS; i++) {
504  GtkWidget *swindow, *image;
505 
506  if (inv_notebooks[i].type == INV_TREE) {
507  swindow = gtk_scrolled_window_new(NULL, NULL);
508  gtk_widget_show(swindow);
509  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow),
510  GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
511  image = gtk_image_new_from_pixbuf(
512  gdk_pixbuf_new_from_xpm_data((const char**) inv_notebooks[i].xpm));
513 
514  if (inv_notebooks[i].tooltip) {
515  GtkWidget *eb;
516 
517  eb = gtk_event_box_new();
518  gtk_widget_show(eb);
519 
520  gtk_container_add(GTK_CONTAINER(eb), image);
521  gtk_widget_show(image);
522 
523  image = eb;
524  gtk_widget_set_tooltip_text(image, inv_notebooks[i].tooltip);
525  }
526 
527  gtk_notebook_insert_page(GTK_NOTEBOOK(inv_notebook), swindow, image, i);
528 
529  inv_notebooks[i].treestore = gtk_tree_store_new(LIST_NUM_COLUMNS,
530  G_TYPE_STRING,
531  G_TYPE_OBJECT,
532  G_TYPE_STRING,
533  G_TYPE_STRING,
534  G_TYPE_POINTER,
535  GDK_TYPE_COLOR,
536  G_TYPE_INT,
537  G_TYPE_STRING,
538  GDK_TYPE_COLOR,
539  PANGO_TYPE_FONT_DESCRIPTION);
540 
541  inv_notebooks[i].treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
542  inv_notebooks[i].treestore));
543 
544  g_signal_connect((gpointer) inv_notebooks[i].treeview, "row_collapsed",
545  G_CALLBACK(list_row_collapse), NULL);
546 
547  setup_list_columns(inv_notebooks[i].treeview);
548  gtk_widget_show(inv_notebooks[i].treeview);
549  gtk_container_add(GTK_CONTAINER(swindow), inv_notebooks[i].treeview);
550  }
551  }
552  num_inv_notebook_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(inv_notebook));
553 
554  /* Make sure we are on the first page */
555  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), 0);
556 
557  /* If all the data is set up properly, these should match */
558  if (num_inv_notebook_pages != NUM_INV_LISTS) {
559  LOG(LOG_ERROR, "inventory.c::inventory_init",
560  "num_inv_notebook_pages (%d) does not match NUM_INV_LISTS(%d)\n",
561  num_inv_notebook_pages, NUM_INV_LISTS);
562  }
563 
564 }
565 
575  draw_lists();
576 }
577 
582 void open_container(item *op) {
583  draw_lists();
584 }
585 
590 void command_show(const char *params) {
591  if (!params) {
592  /*
593  * Shouldn't need to get current page, but next_page call is not
594  * wrapping like the docs claim it should.
595  */
596  if (gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook)) ==
598  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), 0);
599  } else {
600  gtk_notebook_next_page(GTK_NOTEBOOK(inv_notebook));
601  }
602  } else {
603  char buf[MAX_BUF];
604 
605  for (int i = 0; i < NUM_INV_LISTS; i++) {
606  if (!strncmp(params, inv_notebooks[i].name, strlen(params))) {
607  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), i);
608  return;
609  }
610  }
611  snprintf(buf, sizeof (buf), "Unknown notebook page %s\n", params);
613  }
614 }
615 
622 void set_weight_limit(guint32 wlim) {
623  weight_limit = wlim / 1000.0;
624 }
625 
631 static GtkStyle *get_row_style(item *it) {
632  int style;
633 
634  /* Note that this ordering is documented in the sample rc file.
635  * it would be nice if this precedence could be more easily
636  * setable by the end user.
637  */
638  if (it->unpaid) {
639  style = Style_Unpaid;
640  } else if (it->cursed || it->damned) {
641  style = Style_Cursed;
642  } else if (it->magical) {
643  style = Style_Magical;
644  } else if (it->applied) {
645  style = Style_Applied;
646  } else if (it->locked) {
647  style = Style_Locked;
648  } else {
649  return NULL; /* No matching style */
650  }
651 
652  return inv_styles[style];
653 }
654 
655 /***************************************************************************
656  * Below are the actual guts for drawing the inventory and look windows.
657  * Some quick notes:
658  * 1) The gtk2 widgets (treeview/treemodel) seem noticably slower than the
659  * older clist widgets in gtk1. This is beyond the code below - just
660  * scrolling the window, which is all done internally by gtk, is
661  * quite slow. Seems especially bad when using the scrollwheel.
662  * 2) documentation suggests the detaching the treemodel and re-attaching
663  * it after insertions would be faster. The problem is that this causes
664  * loss of positioning for the scrollbar. Eg, you eat a food in the middle
665  * of your inventory, and then inventory resets to the top of the inventory.
666  * 3) it'd probably be much more efficient if the code could know what changes
667  * are taking place, instead of rebuilding the tree model each time. For
668  * example, if the only thing that changed is the number of of the object,
669  * we can just update the name and weight, and not rebuild the entire list.
670  * This may be doable in the code below by getting data from the tree store
671  * and comparing it against what we want to show - however, figuring out
672  * insertions and removals are more difficult.
673  */
674 
676 }
677 
679 }
680 
682 }
683 
697 static void add_object_to_store(item *it, GtkTreeStore *store,
698  GtkTreeIter *new, GtkTreeIter *parent, int color) {
699  char buf[256], buf1[256];
700  GdkColor *foreground = NULL, *background = NULL;
701  PangoFontDescription *font = NULL;
702  GtkStyle *row_style;
703 
704  if (it->weight < 0) {
705  strcpy(buf, " ");
706  } else {
707  snprintf(buf, sizeof (buf), "%6.1f", it->nrof * it->weight);
708  }
709  snprintf(buf1, 255, "%s %s", it->d_name, it->flags);
710  if (color) {
711  row_style = get_row_style(it);
712  if (row_style) {
713  /*
714  * Even if the user doesn't define these, we should still get get
715  * defaults from the system.
716  */
717  foreground = &row_style->text[GTK_STATE_NORMAL];
718  background = &row_style->base[GTK_STATE_NORMAL];
719  font = row_style->font_desc;
720  }
721  }
722 
723  gtk_tree_store_append(store, new, parent); /* Acquire an iterator */
724  gtk_tree_store_set(store, new,
725  LIST_ICON, (GdkPixbuf*) pixmaps[it->face]->icon_image,
726  LIST_NAME, buf1,
727  LIST_WEIGHT, buf,
728  LIST_BACKGROUND, background,
729  LIST_FOREGROUND, foreground,
730  LIST_FONT, font,
731  LIST_OBJECT, it,
732  LIST_TYPE, it->type,
733  LIST_BASENAME, it->s_name,
734  -1);
735 }
736 
741  item *tmp;
742  GtkTreeIter iter;
743  /*
744  * List drawing is actually fairly inefficient - we only know globally if
745  * the objects has changed, but have no idea what specific object has
746  * changed. As such, we are forced to basicly redraw the entire list each
747  * time this is called.
748  */
749  gtk_tree_store_clear(store_look);
750 
751  for (tmp = cpl.below->inv; tmp; tmp = tmp->next) {
752  add_object_to_store(tmp, store_look, &iter, NULL, 1);
753 
754  if ((cpl.container == tmp) && tmp->open) {
755  item *tmp2;
756  GtkTreeIter iter1;
757  GtkTreePath *path;
758 
759  for (tmp2 = tmp->inv; tmp2; tmp2 = tmp2->next) {
760  add_object_to_store(tmp2, store_look, &iter1, &iter, 1);
761  }
762  path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_look), &iter);
763  gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_look), path, FALSE);
764  gtk_tree_path_free(path);
765  }
766  }
767 }
768 
775 static void draw_inv_list(int tab) {
776  item *tmp;
777  GtkTreeIter iter;
778  int rowflag;
779 
780  /*
781  * List drawing is actually fairly inefficient - we only know globally if
782  * the objects has changed, but have no idea what specific object has
783  * changed. As such, we are forced to basicly redraw the entire list each
784  * time this is called.
785  */
786  gtk_tree_store_clear(inv_notebooks[tab].treestore);
787 
788  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
789  rowflag = inv_notebooks[tab].show_func(tmp);
790  if (!(rowflag & INV_SHOW_ITEM)) {
791  continue;
792  }
793 
794  add_object_to_store(tmp, inv_notebooks[tab].treestore, &iter, NULL, rowflag & INV_SHOW_COLOR);
795 
796  if ((cpl.container == tmp) && tmp->open) {
797  item *tmp2;
798  GtkTreeIter iter1;
799  GtkTreePath *path;
800 
801  for (tmp2 = tmp->inv; tmp2; tmp2 = tmp2->next) {
802  /*
803  * Wonder if we really want this logic for objects in
804  * containers? my thought is yes - being able to see all
805  * cursed objects in the container could be quite useful.
806  * Unfortunately, that doesn't quite work as intended, because
807  * we will only get here if the container object is being
808  * displayed. Since container objects can't be cursed, can't
809  * use that as a filter.
810  */
811  /*
812  rowflag = inv_notebooks[tab].show_func(tmp2);
813  */
814  if (!(rowflag & INV_SHOW_ITEM)) {
815  continue;
816  }
817  add_object_to_store(tmp2, inv_notebooks[tab].treestore, &iter1, &iter,
818  rowflag & INV_SHOW_COLOR);
819  }
820  path = gtk_tree_model_get_path(GTK_TREE_MODEL(inv_notebooks[tab].treestore), &iter);
821  gtk_tree_view_expand_row(GTK_TREE_VIEW(inv_notebooks[tab].treeview), path, FALSE);
822  gtk_tree_path_free(path);
823  }
824  }
825 }
826 
835  GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
836  list_item_action(event, (item*) user_data);
837  return TRUE;
838 }
839 
840 static void draw_inv_table_icon(GdkWindow *dst, const void *image) {
841  cairo_t *cr = gdk_cairo_create(dst);
842 
843  gdk_window_clear(dst);
844  gdk_cairo_set_source_pixbuf(cr, (GdkPixbuf *) image, 0, 0);
845  cairo_paint(cr);
846  cairo_destroy(cr);
847 }
848 
856 static gboolean drawingarea_inventory_table_expose_event(GtkWidget *widget,
857  GdkEventExpose *event, gpointer user_data) {
858  item *tmp;
859 
860  tmp = (item*) user_data;
861 
862  /*
863  * Can get cases when switching tabs that we get an expose event before the
864  * list is updated - if so, don't draw stuff we don't have faces for.
865  */
866  if (tmp->face) {
867  draw_inv_table_icon(gtk_widget_get_window(widget), pixmaps[tmp->face]->icon_image);
868  }
869 
870  return TRUE;
871 }
872 
879 static void draw_inv_table(int animate) {
880  int x, y, rows, columns, num_items, i;
881  static int max_drawn = 0;
882  item *tmp;
883  char buf[256];
884  gulong handler;
885 
886  num_items = 0;
887  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
888  num_items++;
889  }
890 
891  GtkAllocation size;
892  gtk_widget_get_allocation(inv_table, &size);
893 
894  columns = size.width / image_size;
895  if (columns > MAX_INV_COLUMNS) {
896  columns = MAX_INV_COLUMNS;
897  }
898  rows = size.height / image_size;
899 
900  if (num_items > columns * rows) {
901  rows = num_items / columns;
902  if (num_items % columns) {
903  rows++;
904  }
905  }
906  if (rows > MAX_INV_ROWS) {
907  rows = MAX_INV_ROWS;
908  }
909 
910  gtk_table_resize(GTK_TABLE(inv_table), rows, columns);
911 
912  x = 0;
913  y = 0;
914  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
915  if (inv_table_children[x][y] == NULL) {
916  inv_table_children[x][y] = gtk_drawing_area_new();
917  gtk_widget_set_size_request(inv_table_children[x][y], image_size,
918  image_size);
919 
920  gtk_table_attach(GTK_TABLE(inv_table), inv_table_children[x][y],
921  x, x + 1, y, y + 1, GTK_FILL, GTK_FILL, 0, 0);
922  }
923  if (animate) {
924  /* This is an object with animations */
925  if (tmp->animation_id > 0 && tmp->anim_speed) {
926  tmp->last_anim++;
927 
928  /* Time to change the face for this one */
929  if (tmp->last_anim >= tmp->anim_speed) {
930  tmp->anim_state++;
931  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
932  tmp->anim_state = 0;
933  }
934  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
935  tmp->last_anim = 0;
936 
938  gtk_widget_get_window(inv_table_children[x][y]),
939  pixmaps[tmp->face]->icon_image);
940  }
941  }
942  /* On animation run, so don't do any of the remaining logic */
943  } else {
944  /*
945  * Need to clear out the old signals, since the signals are
946  * effectively stacked - you can have 6 signal handlers tied to the
947  * same function.
948  */
949  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
950  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
952  NULL);
953 
954  if (handler) {
955  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
956  }
957 
958  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
959  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
961  NULL);
962  if (handler) {
963  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
964  }
965  /*
966  * Not positive precisely what events are needed, but some events
967  * beyond just the button press are necessary for the tooltips to
968  * work.
969  */
970  gtk_widget_add_events(inv_table_children[x][y], GDK_ALL_EVENTS_MASK);
971 
972  g_signal_connect((gpointer) inv_table_children[x][y], "button_press_event",
974  tmp);
975 
976  g_signal_connect((gpointer) inv_table_children[x][y], "expose_event",
978  tmp);
979 
980  /* Draw the inventory icon image to the table. */
981  draw_inv_table_icon(gtk_widget_get_window(inv_table_children[x][y]),
982  pixmaps[tmp->face]->icon_image);
983 
984  // Draw an extra indicator if the item is applied.
985  if (tmp->applied) {
986  gtk_widget_modify_bg(inv_table_children[x][y],
987  GTK_STATE_NORMAL, &applied_color);
988  } else {
989  gtk_widget_modify_bg(inv_table_children[x][y],
990  GTK_STATE_NORMAL, NULL);
991  }
992 
993  gtk_widget_show(inv_table_children[x][y]);
994  /*
995  * Use tooltips to provide additional detail about the icons.
996  * Looking at the code, the tooltip widget will take care of
997  * removing the old tooltip, freeing strings, etc.
998  */
999  snprintf(buf, 255, "%s %s", tmp->d_name, tmp->flags);
1000  gtk_widget_set_tooltip_text(inv_table_children[x][y], buf);
1001  }
1002  x++;
1003  if (x == columns) {
1004  x = 0;
1005  y++;
1006  }
1007 
1008  }
1009  /* Don't need to do the logic below if only doing animation run */
1010  if (animate) {
1011  return;
1012  }
1013  /*
1014  * Need to disconnect the callback functions cells we did not draw.
1015  * otherwise, we get errors on objects that are drawn.
1016  */
1017  for (i = num_items; i <= max_drawn; i++) {
1018  if (inv_table_children[x][y]) {
1019  gdk_window_clear(gtk_widget_get_window(inv_table_children[x][y]));
1020 
1021  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
1022  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
1024  NULL);
1025 
1026  if (handler) {
1027  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
1028  }
1029 
1030  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
1031  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
1033  NULL);
1034  if (handler) {
1035  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
1036  }
1037 
1038  /* Hide the widget so that the tooltips doesn't show up */
1039  gtk_widget_hide(inv_table_children[x][y]);
1040  }
1041  x++;
1042  if (x == columns) {
1043  x = 0;
1044  y++;
1045  }
1046  }
1047  max_drawn = num_items;
1048 
1049  gtk_widget_show(inv_table);
1050 }
1051 
1058 static void draw_inv(int tab) {
1059  char buf[256];
1060 
1061  snprintf(buf, sizeof (buf), "%6.1f", cpl.ob->weight);
1062  gtk_label_set_text(GTK_LABEL(encumbrance_current), buf);
1063  snprintf(buf, sizeof (buf), "%6.1f", weight_limit);
1064  gtk_label_set_text(GTK_LABEL(encumbrance_max), buf);
1065 
1066  if (inv_notebooks[tab].type == INV_TREE) {
1067  draw_inv_list(tab);
1068  } else if (inv_notebooks[tab].type == INV_TABLE) {
1069  draw_inv_table(0);
1070  }
1071 }
1072 
1076 void draw_lists() {
1077  /*
1078  * There are some extra complications with container handling and timing.
1079  * For example, we draw the window before we get a list of the container,
1080  * and then the container contents are not drawn - this can be handled by
1081  * looking at container->inv_updated.
1082  */
1084  cpl.container->env->inv_updated = 1;
1085  cpl.container->inv_updated = 0;
1086  }
1087  if (cpl.ob->inv_updated) {
1088  draw_inv(gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook)));
1089  cpl.ob->inv_updated = 0;
1090  }
1091  if (cpl.below->inv_updated) {
1092  draw_look_list();
1093  cpl.below->inv_updated = 0;
1094  }
1095 }
1096 
1109 static void on_switch_page(GtkNotebook *notebook, gpointer *page,
1110  guint page_num, gpointer user_data) {
1111  guint oldpage;
1112 
1113  oldpage = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
1114  if (oldpage != page_num && inv_notebooks[oldpage].type == INV_TREE) {
1115  gtk_tree_store_clear(inv_notebooks[oldpage].treestore);
1116  }
1117  cpl.ob->inv_updated = 1;
1118 }
1119 
1127 static gboolean on_inv_table_expose_event(GtkWidget *widget,
1128  GdkEventExpose *event, gpointer user_data) {
1129  draw_inv_table(0);
1130  return TRUE;
1131 }
1132 
1136 static void animate_inventory() {
1137  gboolean valid;
1138  GtkTreeIter iter;
1139  item *tmp;
1140  int page;
1141  GtkTreeStore *store;
1142  static int inv_tick = 0;
1143 
1144  /*
1145  * If global tick is set, then we are getting tick events from server to
1146  * keep in sync, so we don't need the logic below.
1147  */
1148  if (!tick) {
1149  /*
1150  * The gtk client timeout is 12 times faster than that of the server so
1151  * we slow it down here. If we were really clever, we'd find what the
1152  * timeout on the server actually is, and do gettimeofday calls here to
1153  * remain very closely in sync.
1154  */
1155  inv_tick++;
1156  if (inv_tick < 12) {
1157  return;
1158  }
1159  inv_tick = 0;
1160  }
1161 
1162  page = gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook));
1163 
1164  /* Still need to do logic for the table view. */
1165  if (inv_notebooks[page].type == INV_TABLE) {
1166  draw_inv_table(1);
1167  return;
1168  }
1169 
1170  store = inv_notebooks[page].treestore;
1171 
1172  /* Get the first iter in the list */
1173  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
1174 
1175  while (valid) {
1176  gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1177  LIST_OBJECT, &tmp,
1178  -1);
1179 
1180  /* This is an object with animations */
1181  if (tmp->animation_id > 0 && tmp->anim_speed &&
1182  animations[tmp->animation_id].faces != NULL) {
1183  tmp->last_anim++;
1184 
1185  /* Time to change the face for this one */
1186  if (tmp->last_anim >= tmp->anim_speed) {
1187  tmp->anim_state++;
1188  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
1189  tmp->anim_state = 0;
1190  }
1191  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
1192  tmp->last_anim = 0;
1193 
1194  /* Update image in the tree store */
1195  gtk_tree_store_set(store, &iter,
1196  LIST_ICON, (GdkPixbuf*) pixmaps[tmp->face]->icon_image,
1197  -1);
1198  }
1199  }
1200  valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1201  }
1202 }
1203 
1207 static void animate_look() {
1208  gboolean valid;
1209  GtkTreeIter iter;
1210  item *tmp;
1211  static int inv_tick = 0;
1212 
1213  /*
1214  * If global tick is set, then we are getting tick events from server to
1215  * keep in sync, so we don't need the logic below.
1216  */
1217  if (!tick) {
1218  /*
1219  * The gtk client timeout is 12 times faster than that of the server so
1220  * we slow it down here. If we were really clever, we'd find what the
1221  * timeout on the server actually is, and do gettimeofday calls here to
1222  * remain very closely in sync.
1223  */
1224  inv_tick++;
1225  if (inv_tick < 12) {
1226  return;
1227  }
1228  inv_tick = 0;
1229  }
1230 
1231  /* Get the first iter in the list */
1232  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store_look), &iter);
1233 
1234  while (valid) {
1235  gtk_tree_model_get(GTK_TREE_MODEL(store_look), &iter,
1236  LIST_OBJECT, &tmp,
1237  -1);
1238 
1239  /* This is an object with animations */
1240  if (tmp->animation_id > 0 && tmp->anim_speed) {
1241  tmp->last_anim++;
1242 
1243  /* Time to change the face for this one */
1244  if (tmp->last_anim >= tmp->anim_speed) {
1245  tmp->anim_state++;
1246  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
1247  tmp->anim_state = 0;
1248  }
1249  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
1250  tmp->last_anim = 0;
1251 
1252  /* Update image in the tree store */
1253  gtk_tree_store_set(store_look, &iter,
1254  LIST_ICON, (GdkPixbuf*) pixmaps[tmp->face]->icon_image,
1255  -1);
1256  }
1257  }
1258  valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store_look), &iter);
1259  }
1260 }
1261 
1268  animate_look();
1269 }
float weight
Definition: item.h:61
Animations animations[MAXANIM]
Definition: commands.c:1128
static const GdkColor applied_color
Definition: inventory.c:41
GtkWidget * spinbutton_count
Definition: keys.c:63
static GtkWidget * inv_table
Definition: inventory.c:47
GtkTreeStore * treestore
Definition: inventory.c:102
static void list_row_collapse(GtkTreeView *treeview, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
Definition: inventory.c:322
char s_name[NAME_LEN]
Definition: item.h:56
static GtkTreeStore * store_look
Definition: inventory.c:43
static void draw_inv(int tab)
Definition: inventory.c:1058
GtkBuilder * window_xml
Definition: main.c:86
static void animate_look()
Definition: inventory.c:1207
int image_size
Definition: image.c:36
guint32 tick
Definition: client.c:61
static void draw_inv_table(int animate)
Definition: inventory.c:879
void command_show(const char *params)
Definition: inventory.c:590
static int get_item_env(item *it)
Definition: inventory.c:195
guint8 num_animations
Definition: client.h:100
static int show_magical(item *it)
Definition: inventory.c:132
static void setup_list_columns(GtkWidget *treeview)
Definition: inventory.c:337
char flags[NAME_LEN]
Definition: item.h:58
static int num_inv_notebook_pages
Definition: inventory.c:90
guint16 inv_updated
Definition: item.h:76
item * ob
Definition: client.h:336
struct item_struct * env
Definition: item.h:53
static int show_locked(item *it)
Definition: inventory.c:140
static GtkStyle * inv_styles[Style_Last]
Definition: inventory.c:71
const char * tooltip
Definition: inventory.c:94
struct item_struct * next
Definition: item.h:51
void item_event_container_clearing(item *container)
Definition: inventory.c:678
static gboolean drawingarea_inventory_table_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition: inventory.c:834
#define MAX_INV_ROWS
Definition: inventory.c:57
#define MSG_TYPE_CLIENT
Definition: newclient.h:395
static int show_unpaid(item *it)
Definition: inventory.c:124
static void animate_inventory()
Definition: inventory.c:1136
static gboolean drawingarea_inventory_table_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
Definition: inventory.c:856
#define INV_SHOW_ITEM
Definition: inventory.c:79
GtkWidget * treeview_look
Definition: inventory.c:38
static int show_unidentified(item *it)
Definition: inventory.c:149
#define NDI_BLACK
Definition: newclient.h:221
static GtkWidget * encumbrance_max
Definition: inventory.c:45
gint32 tag
Definition: item.h:59
guint32 nrof
Definition: item.h:60
guint16 locked
Definition: item.h:72
static const char * Style_Names[Style_Last]
Definition: inventory.c:66
static int show_unapplied(item *it)
Definition: inventory.c:120
guint16 type
Definition: item.h:81
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:109
void client_send_apply(int tag)
Definition: player.c:69
#define MSG_TYPE_CLIENT_ERROR
Definition: newclient.h:667
#define NDI_RED
Definition: newclient.h:224
static void add_object_to_store(item *it, GtkTreeStore *store, GtkTreeIter *new, GtkTreeIter *parent, int color)
Definition: inventory.c:697
static int show_all(item *it)
Definition: inventory.c:112
GtkWidget * window_root
Definition: main.c:87
static int show_applied(item *it)
Definition: inventory.c:116
#define MSG_TYPE_CLIENT_NOTICE
Definition: newclient.h:664
void close_container(item *op)
Definition: inventory.c:574
list_property
Definition: inventory.c:171
char d_name[NAME_LEN]
Definition: item.h:55
guint16 last_anim
Definition: item.h:66
item * below
Definition: client.h:337
Client_Player cpl
Definition: client.c:68
item * container
Definition: client.h:339
void draw_lists()
Definition: inventory.c:1076
void inventory_init(GtkWidget *window_root)
Definition: inventory.c:453
void client_send_move(int loc, int tag, int nrof)
Definition: player.c:80
guint32 flagsval
Definition: item.h:80
static void draw_inv_list(int tab)
Definition: inventory.c:775
#define MAX_BUF
Definition: client.h:40
item_env
Definition: inventory.c:181
void set_weight_limit(guint32 wlim)
Definition: inventory.c:622
static GtkWidget * encumbrance_current
Definition: inventory.c:44
guint16 animation_id
Definition: item.h:63
static GtkStyle * get_row_style(item *it)
Definition: inventory.c:631
#define INV_SHOW_COLOR
Definition: inventory.c:80
void send_mark_obj(item *op)
Definition: item.c:614
static gboolean on_inv_table_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
Definition: inventory.c:1127
guint8 anim_speed
Definition: item.h:64
guint16 unpaid
Definition: item.h:71
static void draw_inv_table_icon(GdkWindow *dst, const void *image)
Definition: inventory.c:840
static double weight_limit
Definition: inventory.c:49
void toggle_locked(item *op)
Definition: item.c:596
guint32 count
Definition: client.h:359
static void on_switch_page(GtkNotebook *notebook, gpointer *page, guint page_num, gpointer user_data)
Definition: inventory.c:1109
const char * name
Definition: inventory.c:93
static gboolean list_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer userdata)
Definition: inventory.c:282
Warning that something definitely didn&#39;t work.
Definition: client.h:444
#define NUM_INV_LISTS
Definition: inventory.c:78
#define F_UNIDENTIFIED
Definition: newclient.h:259
guint16 open
Definition: item.h:74
display_type
Definition: inventory.c:86
static int has_init
Definition: account.c:94
void item_event_item_changed(item *it)
Definition: inventory.c:681
void inventory_get_styles()
Definition: inventory.c:426
Styles
Definition: inventory.c:61
void draw_ext_info(int orig_color, int type, int subtype, const char *message)
Definition: info.c:932
static void list_item_action(GdkEventButton *event, item *tmp)
Definition: inventory.c:213
static GtkWidget * inv_notebook
Definition: inventory.c:46
guint16 * faces
Definition: client.h:107
GtkWidget * treeview
Definition: inventory.c:101
static int show_unlocked(item *it)
Definition: inventory.c:144
guint16 damned
Definition: item.h:69
PixmapInfo * pixmaps[MAXPIXMAPNUM]
Definition: image.c:47
void client_send_examine(int tag)
Definition: player.c:73
guint16 cursed
Definition: item.h:68
guint16 magical
Definition: item.h:67
guint16 applied
Definition: item.h:73
static int show_nonmagical(item *it)
Definition: inventory.c:136
char * name
Definition: image.c:39
gint16 face
Definition: item.h:62
static int show_cursed(item *it)
Definition: inventory.c:128
void open_container(item *op)
Definition: inventory.c:582
guint8 anim_state
Definition: item.h:65
struct item_struct * inv
Definition: item.h:54
static Notebook_Info inv_notebooks[NUM_INV_LISTS]
Definition: inventory.c:153
void item_event_item_deleting(item *it)
Definition: inventory.c:675
GdkPixbuf * icon_image
Definition: image.h:49
#define MAX_INV_COLUMNS
Definition: inventory.c:56
Minor, non-harmful issues.
Definition: client.h:442
const char *const * xpm
Definition: inventory.c:95
static GtkWidget * inv_table_children[MAX_INV_ROWS][MAX_INV_COLUMNS]
Definition: inventory.c:58
void inventory_tick()
Definition: inventory.c:1266
int(* show_func)(item *it)
Definition: inventory.c:96
void draw_look_list()
Definition: inventory.c:740