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