Crossfire Client, Trunk  R20612
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  return (it->locked ? 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) inv_table, "expose_event",
471  (GCallback) on_inv_table_expose_event, NULL);
472  g_signal_connect((gpointer) treeview_look, "row_collapsed",
473  (GCallback) list_row_collapse, NULL);
474 
475  memset(inv_table_children, 0, sizeof (GtkWidget *) * MAX_INV_ROWS * MAX_INV_COLUMNS);
476 
477  store_look = gtk_tree_store_new(LIST_NUM_COLUMNS,
478  G_TYPE_STRING,
479  G_TYPE_OBJECT,
480  G_TYPE_STRING,
481  G_TYPE_STRING,
482  G_TYPE_POINTER,
483  GDK_TYPE_COLOR,
484  G_TYPE_INT,
485  G_TYPE_STRING,
486  GDK_TYPE_COLOR,
487  PANGO_TYPE_FONT_DESCRIPTION);
488 
489  gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_look), GTK_TREE_MODEL(store_look));
490  setup_list_columns(treeview_look);
491  /*
492  * Glade doesn't let us fully realize a treeview widget - we still need to
493  * to do a bunch of customization just like we do for the look window
494  * above. If we have to do all that work, might as well just put it in the
495  * for loop below vs setting up half realized widgets within layout that we
496  * then need to finish setting up. However, that said, we want to be able
497  * to set up other notebooks within layout for perhaps a true list of just
498  * icons. So we presume that any tabs that exist must already be all set
499  * up. We prepend our tabs to the existing tab - this makes the position
500  * of the array of noteboks correspond to actual data in the tabs.
501  */
502  for (i = 0; i < NUM_INV_LISTS; i++) {
503  GtkWidget *swindow, *image;
504 
505  if (inv_notebooks[i].type == INV_TREE) {
506  swindow = gtk_scrolled_window_new(NULL, NULL);
507  gtk_widget_show(swindow);
508  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swindow),
509  GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
510  image = gtk_image_new_from_pixbuf(
511  gdk_pixbuf_new_from_xpm_data((const char**) inv_notebooks[i].xpm));
512 
513  if (inv_notebooks[i].tooltip) {
514  GtkWidget *eb;
515 
516  eb = gtk_event_box_new();
517  gtk_widget_show(eb);
518 
519  gtk_container_add(GTK_CONTAINER(eb), image);
520  gtk_widget_show(image);
521 
522  image = eb;
523  gtk_widget_set_tooltip_text(image, inv_notebooks[i].tooltip);
524  }
525 
526  gtk_notebook_insert_page(GTK_NOTEBOOK(inv_notebook), swindow, image, i);
527 
528  inv_notebooks[i].treestore = gtk_tree_store_new(LIST_NUM_COLUMNS,
529  G_TYPE_STRING,
530  G_TYPE_OBJECT,
531  G_TYPE_STRING,
532  G_TYPE_STRING,
533  G_TYPE_POINTER,
534  GDK_TYPE_COLOR,
535  G_TYPE_INT,
536  G_TYPE_STRING,
537  GDK_TYPE_COLOR,
538  PANGO_TYPE_FONT_DESCRIPTION);
539 
540  inv_notebooks[i].treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
541  inv_notebooks[i].treestore));
542 
543  g_signal_connect((gpointer) inv_notebooks[i].treeview, "row_collapsed",
544  G_CALLBACK(list_row_collapse), NULL);
545 
546  setup_list_columns(inv_notebooks[i].treeview);
547  gtk_widget_show(inv_notebooks[i].treeview);
548  gtk_container_add(GTK_CONTAINER(swindow), inv_notebooks[i].treeview);
549  }
550  }
551  num_inv_notebook_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(inv_notebook));
552 
553  /* Make sure we are on the first page */
554  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), 0);
555 
556  /* If all the data is set up properly, these should match */
557  if (num_inv_notebook_pages != NUM_INV_LISTS) {
558  LOG(LOG_ERROR, "inventory.c::inventory_init",
559  "num_inv_notebook_pages (%d) does not match NUM_INV_LISTS(%d)\n",
560  num_inv_notebook_pages, NUM_INV_LISTS);
561  }
562 
563 }
564 
574  draw_lists();
575 }
576 
581 void open_container(item *op) {
582  draw_lists();
583 }
584 
589 void command_show(const char *params) {
590  if (!params) {
591  /*
592  * Shouldn't need to get current page, but next_page call is not
593  * wrapping like the docs claim it should.
594  */
595  if (gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook)) ==
597  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), 0);
598  } else {
599  gtk_notebook_next_page(GTK_NOTEBOOK(inv_notebook));
600  }
601  } else {
602  char buf[MAX_BUF];
603 
604  for (int i = 0; i < NUM_INV_LISTS; i++) {
605  if (!strncmp(params, inv_notebooks[i].name, strlen(params))) {
606  gtk_notebook_set_current_page(GTK_NOTEBOOK(inv_notebook), i);
607  return;
608  }
609  }
610  snprintf(buf, sizeof (buf), "Unknown notebook page %s\n", params);
612  }
613 }
614 
621 void set_weight_limit(guint32 wlim) {
622  weight_limit = wlim / 1000.0;
623 }
624 
630 static GtkStyle *get_row_style(item *it) {
631  int style;
632 
633  /* Note that this ordering is documented in the sample rc file.
634  * it would be nice if this precedence could be more easily
635  * setable by the end user.
636  */
637  if (it->unpaid) {
638  style = Style_Unpaid;
639  } else if (it->cursed || it->damned) {
640  style = Style_Cursed;
641  } else if (it->magical) {
642  style = Style_Magical;
643  } else if (it->applied) {
644  style = Style_Applied;
645  } else if (it->locked) {
646  style = Style_Locked;
647  } else {
648  return NULL; /* No matching style */
649  }
650 
651  return inv_styles[style];
652 }
653 
654 /***************************************************************************
655  * Below are the actual guts for drawing the inventory and look windows.
656  * Some quick notes:
657  * 1) The gtk2 widgets (treeview/treemodel) seem noticably slower than the
658  * older clist widgets in gtk1. This is beyond the code below - just
659  * scrolling the window, which is all done internally by gtk, is
660  * quite slow. Seems especially bad when using the scrollwheel.
661  * 2) documentation suggests the detaching the treemodel and re-attaching
662  * it after insertions would be faster. The problem is that this causes
663  * loss of positioning for the scrollbar. Eg, you eat a food in the middle
664  * of your inventory, and then inventory resets to the top of the inventory.
665  * 3) it'd probably be much more efficient if the code could know what changes
666  * are taking place, instead of rebuilding the tree model each time. For
667  * example, if the only thing that changed is the number of of the object,
668  * we can just update the name and weight, and not rebuild the entire list.
669  * This may be doable in the code below by getting data from the tree store
670  * and comparing it against what we want to show - however, figuring out
671  * insertions and removals are more difficult.
672  */
673 
675 }
676 
678 }
679 
681 }
682 
696 static void add_object_to_store(item *it, GtkTreeStore *store,
697  GtkTreeIter *new, GtkTreeIter *parent, int color) {
698  char buf[256], buf1[256];
699  GdkColor *foreground = NULL, *background = NULL;
700  PangoFontDescription *font = NULL;
701  GtkStyle *row_style;
702 
703  if (it->weight < 0) {
704  strcpy(buf, " ");
705  } else {
706  snprintf(buf, sizeof (buf), "%6.1f", it->nrof * it->weight);
707  }
708  snprintf(buf1, 255, "%s %s", it->d_name, it->flags);
709  if (color) {
710  row_style = get_row_style(it);
711  if (row_style) {
712  /*
713  * Even if the user doesn't define these, we should still get get
714  * defaults from the system.
715  */
716  foreground = &row_style->text[GTK_STATE_NORMAL];
717  background = &row_style->base[GTK_STATE_NORMAL];
718  font = row_style->font_desc;
719  }
720  }
721 
722  gtk_tree_store_append(store, new, parent); /* Acquire an iterator */
723  gtk_tree_store_set(store, new,
724  LIST_ICON, (GdkPixbuf*) pixmaps[it->face]->icon_image,
725  LIST_NAME, buf1,
726  LIST_WEIGHT, buf,
727  LIST_BACKGROUND, background,
728  LIST_FOREGROUND, foreground,
729  LIST_FONT, font,
730  LIST_OBJECT, it,
731  LIST_TYPE, it->type,
732  LIST_BASENAME, it->s_name,
733  -1);
734 }
735 
740  item *tmp;
741  GtkTreeIter iter;
742  /*
743  * List drawing is actually fairly inefficient - we only know globally if
744  * the objects has changed, but have no idea what specific object has
745  * changed. As such, we are forced to basicly redraw the entire list each
746  * time this is called.
747  */
748  gtk_tree_store_clear(store_look);
749 
750  for (tmp = cpl.below->inv; tmp; tmp = tmp->next) {
751  add_object_to_store(tmp, store_look, &iter, NULL, 1);
752 
753  if ((cpl.container == tmp) && tmp->open) {
754  item *tmp2;
755  GtkTreeIter iter1;
756  GtkTreePath *path;
757 
758  for (tmp2 = tmp->inv; tmp2; tmp2 = tmp2->next) {
759  add_object_to_store(tmp2, store_look, &iter1, &iter, 1);
760  }
761  path = gtk_tree_model_get_path(GTK_TREE_MODEL(store_look), &iter);
762  gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_look), path, FALSE);
763  gtk_tree_path_free(path);
764  }
765  }
766 }
767 
774 static void draw_inv_list(int tab) {
775  item *tmp;
776  GtkTreeIter iter;
777  int rowflag;
778 
779  /*
780  * List drawing is actually fairly inefficient - we only know globally if
781  * the objects has changed, but have no idea what specific object has
782  * changed. As such, we are forced to basicly redraw the entire list each
783  * time this is called.
784  */
785  gtk_tree_store_clear(inv_notebooks[tab].treestore);
786 
787  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
788  rowflag = inv_notebooks[tab].show_func(tmp);
789  if (!(rowflag & INV_SHOW_ITEM)) {
790  continue;
791  }
792 
793  add_object_to_store(tmp, inv_notebooks[tab].treestore, &iter, NULL, rowflag & INV_SHOW_COLOR);
794 
795  if ((cpl.container == tmp) && tmp->open) {
796  item *tmp2;
797  GtkTreeIter iter1;
798  GtkTreePath *path;
799 
800  for (tmp2 = tmp->inv; tmp2; tmp2 = tmp2->next) {
801  /*
802  * Wonder if we really want this logic for objects in
803  * containers? my thought is yes - being able to see all
804  * cursed objects in the container could be quite useful.
805  * Unfortunately, that doesn't quite work as intended, because
806  * we will only get here if the container object is being
807  * displayed. Since container objects can't be cursed, can't
808  * use that as a filter.
809  */
810  /*
811  rowflag = inv_notebooks[tab].show_func(tmp2);
812  */
813  if (!(rowflag & INV_SHOW_ITEM)) {
814  continue;
815  }
816  add_object_to_store(tmp2, inv_notebooks[tab].treestore, &iter1, &iter,
817  rowflag & INV_SHOW_COLOR);
818  }
819  path = gtk_tree_model_get_path(GTK_TREE_MODEL(inv_notebooks[tab].treestore), &iter);
820  gtk_tree_view_expand_row(GTK_TREE_VIEW(inv_notebooks[tab].treeview), path, FALSE);
821  gtk_tree_path_free(path);
822  }
823  }
824 }
825 
834  GtkWidget *widget, GdkEventButton *event, gpointer user_data) {
835  list_item_action(event, (item*) user_data);
836  return TRUE;
837 }
838 
839 static void draw_inv_table_icon(GdkWindow *dst, const void *image) {
840  cairo_t *cr = gdk_cairo_create(dst);
841 
842  gdk_window_clear(dst);
843  gdk_cairo_set_source_pixbuf(cr, (GdkPixbuf *) image, 0, 0);
844  cairo_paint(cr);
845  cairo_destroy(cr);
846 }
847 
855 static gboolean drawingarea_inventory_table_expose_event(GtkWidget *widget,
856  GdkEventExpose *event, gpointer user_data) {
857  item *tmp;
858 
859  tmp = (item*) user_data;
860 
861  /*
862  * Can get cases when switching tabs that we get an expose event before the
863  * list is updated - if so, don't draw stuff we don't have faces for.
864  */
865  if (tmp->face) {
866  draw_inv_table_icon(gtk_widget_get_window(widget), pixmaps[tmp->face]->icon_image);
867  }
868 
869  return TRUE;
870 }
871 
878 static void draw_inv_table(int animate) {
879  int x, y, rows, columns, num_items, i;
880  static int max_drawn = 0;
881  item *tmp;
882  char buf[256];
883  gulong handler;
884 
885  num_items = 0;
886  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
887  num_items++;
888  }
889 
890  GtkAllocation size;
891  gtk_widget_get_allocation(inv_table, &size);
892 
893  columns = size.width / image_size;
894  if (columns > MAX_INV_COLUMNS) {
895  columns = MAX_INV_COLUMNS;
896  }
897  rows = size.height / image_size;
898 
899  if (num_items > columns * rows) {
900  rows = num_items / columns;
901  if (num_items % columns) {
902  rows++;
903  }
904  }
905  if (rows > MAX_INV_ROWS) {
906  rows = MAX_INV_ROWS;
907  }
908 
909  gtk_table_resize(GTK_TABLE(inv_table), rows, columns);
910 
911  x = 0;
912  y = 0;
913  for (tmp = cpl.ob->inv; tmp; tmp = tmp->next) {
914  if (inv_table_children[x][y] == NULL) {
915  inv_table_children[x][y] = gtk_drawing_area_new();
916  gtk_widget_set_size_request(inv_table_children[x][y], image_size,
917  image_size);
918 
919  gtk_table_attach(GTK_TABLE(inv_table), inv_table_children[x][y],
920  x, x + 1, y, y + 1, GTK_FILL, GTK_FILL, 0, 0);
921  }
922  if (animate) {
923  /* This is an object with animations */
924  if (tmp->animation_id > 0 && tmp->anim_speed) {
925  tmp->last_anim++;
926 
927  /* Time to change the face for this one */
928  if (tmp->last_anim >= tmp->anim_speed) {
929  tmp->anim_state++;
930  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
931  tmp->anim_state = 0;
932  }
933  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
934  tmp->last_anim = 0;
935 
937  gtk_widget_get_window(inv_table_children[x][y]),
938  pixmaps[tmp->face]->icon_image);
939  }
940  }
941  /* On animation run, so don't do any of the remaining logic */
942  } else {
943  /*
944  * Need to clear out the old signals, since the signals are
945  * effectively stacked - you can have 6 signal handlers tied to the
946  * same function.
947  */
948  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
949  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
951  NULL);
952 
953  if (handler) {
954  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
955  }
956 
957  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
958  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
960  NULL);
961  if (handler) {
962  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
963  }
964  /*
965  * Not positive precisely what events are needed, but some events
966  * beyond just the button press are necessary for the tooltips to
967  * work.
968  */
969  gtk_widget_add_events(inv_table_children[x][y], GDK_ALL_EVENTS_MASK);
970 
971  g_signal_connect((gpointer) inv_table_children[x][y], "button_press_event",
973  tmp);
974 
975  g_signal_connect((gpointer) inv_table_children[x][y], "expose_event",
977  tmp);
978 
979  /* Draw the inventory icon image to the table. */
980  draw_inv_table_icon(gtk_widget_get_window(inv_table_children[x][y]),
981  pixmaps[tmp->face]->icon_image);
982 
983  // Draw an extra indicator if the item is applied.
984  if (tmp->applied) {
985  gtk_widget_modify_bg(inv_table_children[x][y],
986  GTK_STATE_NORMAL, &applied_color);
987  } else {
988  gtk_widget_modify_bg(inv_table_children[x][y],
989  GTK_STATE_NORMAL, NULL);
990  }
991 
992  gtk_widget_show(inv_table_children[x][y]);
993  /*
994  * Use tooltips to provide additional detail about the icons.
995  * Looking at the code, the tooltip widget will take care of
996  * removing the old tooltip, freeing strings, etc.
997  */
998  snprintf(buf, 255, "%s %s", tmp->d_name, tmp->flags);
999  gtk_widget_set_tooltip_text(inv_table_children[x][y], buf);
1000  }
1001  x++;
1002  if (x == columns) {
1003  x = 0;
1004  y++;
1005  }
1006 
1007  }
1008  /* Don't need to do the logic below if only doing animation run */
1009  if (animate) {
1010  return;
1011  }
1012  /*
1013  * Need to disconnect the callback functions cells we did not draw.
1014  * otherwise, we get errors on objects that are drawn.
1015  */
1016  for (i = num_items; i <= max_drawn; i++) {
1017  if (inv_table_children[x][y]) {
1018  gdk_window_clear(gtk_widget_get_window(inv_table_children[x][y]));
1019 
1020  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
1021  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
1023  NULL);
1024 
1025  if (handler) {
1026  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
1027  }
1028 
1029  handler = g_signal_handler_find((gpointer) inv_table_children[x][y],
1030  G_SIGNAL_MATCH_FUNC, 0, 0, NULL,
1032  NULL);
1033  if (handler) {
1034  g_signal_handler_disconnect((gpointer) inv_table_children[x][y], handler);
1035  }
1036 
1037  /* Hide the widget so that the tooltips doesn't show up */
1038  gtk_widget_hide(inv_table_children[x][y]);
1039  }
1040  x++;
1041  if (x == columns) {
1042  x = 0;
1043  y++;
1044  }
1045  }
1046  max_drawn = num_items;
1047 
1048  gtk_widget_show(inv_table);
1049 }
1050 
1057 static void draw_inv(int tab) {
1058  char buf[256];
1059 
1060  snprintf(buf, sizeof (buf), "%6.1f", cpl.ob->weight);
1061  gtk_label_set_text(GTK_LABEL(encumbrance_current), buf);
1062  snprintf(buf, sizeof (buf), "%6.1f", weight_limit);
1063  gtk_label_set_text(GTK_LABEL(encumbrance_max), buf);
1064 
1065  if (inv_notebooks[tab].type == INV_TREE) {
1066  draw_inv_list(tab);
1067  } else if (inv_notebooks[tab].type == INV_TABLE) {
1068  draw_inv_table(0);
1069  }
1070 }
1071 
1075 void draw_lists() {
1076  /*
1077  * There are some extra complications with container handling and timing.
1078  * For example, we draw the window before we get a list of the container,
1079  * and then the container contents are not drawn - this can be handled by
1080  * looking at container->inv_updated.
1081  */
1083  cpl.container->env->inv_updated = 1;
1084  cpl.container->inv_updated = 0;
1085  }
1086  if (cpl.ob->inv_updated) {
1087  draw_inv(gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook)));
1088  cpl.ob->inv_updated = 0;
1089  }
1090  if (cpl.below->inv_updated) {
1091  draw_look_list();
1092  cpl.below->inv_updated = 0;
1093  }
1094 }
1095 
1108 static void on_switch_page(GtkNotebook *notebook, gpointer *page,
1109  guint page_num, gpointer user_data) {
1110  guint oldpage;
1111 
1112  oldpage = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
1113  if (oldpage != page_num && inv_notebooks[oldpage].type == INV_TREE) {
1114  gtk_tree_store_clear(inv_notebooks[oldpage].treestore);
1115  }
1116  cpl.ob->inv_updated = 1;
1117 }
1118 
1126 static gboolean on_inv_table_expose_event(GtkWidget *widget,
1127  GdkEventExpose *event, gpointer user_data) {
1128  draw_inv_table(0);
1129  return TRUE;
1130 }
1131 
1135 static void animate_inventory() {
1136  gboolean valid;
1137  GtkTreeIter iter;
1138  item *tmp;
1139  int page;
1140  GtkTreeStore *store;
1141  static int inv_tick = 0;
1142 
1143  /*
1144  * If global tick is set, then we are getting tick events from server to
1145  * keep in sync, so we don't need the logic below.
1146  */
1147  if (!tick) {
1148  /*
1149  * The gtk client timeout is 12 times faster than that of the server so
1150  * we slow it down here. If we were really clever, we'd find what the
1151  * timeout on the server actually is, and do gettimeofday calls here to
1152  * remain very closely in sync.
1153  */
1154  inv_tick++;
1155  if (inv_tick < 12) {
1156  return;
1157  }
1158  inv_tick = 0;
1159  }
1160 
1161  page = gtk_notebook_get_current_page(GTK_NOTEBOOK(inv_notebook));
1162 
1163  /* Still need to do logic for the table view. */
1164  if (inv_notebooks[page].type == INV_TABLE) {
1165  draw_inv_table(1);
1166  return;
1167  }
1168 
1169  store = inv_notebooks[page].treestore;
1170 
1171  /* Get the first iter in the list */
1172  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
1173 
1174  while (valid) {
1175  gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
1176  LIST_OBJECT, &tmp,
1177  -1);
1178 
1179  /* This is an object with animations */
1180  if (tmp->animation_id > 0 && tmp->anim_speed &&
1181  animations[tmp->animation_id].faces != NULL) {
1182  tmp->last_anim++;
1183 
1184  /* Time to change the face for this one */
1185  if (tmp->last_anim >= tmp->anim_speed) {
1186  tmp->anim_state++;
1187  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
1188  tmp->anim_state = 0;
1189  }
1190  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
1191  tmp->last_anim = 0;
1192 
1193  /* Update image in the tree store */
1194  gtk_tree_store_set(store, &iter,
1195  LIST_ICON, (GdkPixbuf*) pixmaps[tmp->face]->icon_image,
1196  -1);
1197  }
1198  }
1199  valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
1200  }
1201 }
1202 
1206 static void animate_look() {
1207  gboolean valid;
1208  GtkTreeIter iter;
1209  item *tmp;
1210  static int inv_tick = 0;
1211 
1212  /*
1213  * If global tick is set, then we are getting tick events from server to
1214  * keep in sync, so we don't need the logic below.
1215  */
1216  if (!tick) {
1217  /*
1218  * The gtk client timeout is 12 times faster than that of the server so
1219  * we slow it down here. If we were really clever, we'd find what the
1220  * timeout on the server actually is, and do gettimeofday calls here to
1221  * remain very closely in sync.
1222  */
1223  inv_tick++;
1224  if (inv_tick < 12) {
1225  return;
1226  }
1227  inv_tick = 0;
1228  }
1229 
1230  /* Get the first iter in the list */
1231  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store_look), &iter);
1232 
1233  while (valid) {
1234  gtk_tree_model_get(GTK_TREE_MODEL(store_look), &iter,
1235  LIST_OBJECT, &tmp,
1236  -1);
1237 
1238  /* This is an object with animations */
1239  if (tmp->animation_id > 0 && tmp->anim_speed) {
1240  tmp->last_anim++;
1241 
1242  /* Time to change the face for this one */
1243  if (tmp->last_anim >= tmp->anim_speed) {
1244  tmp->anim_state++;
1245  if (tmp->anim_state >= animations[tmp->animation_id].num_animations) {
1246  tmp->anim_state = 0;
1247  }
1248  tmp->face = animations[tmp->animation_id].faces[tmp->anim_state];
1249  tmp->last_anim = 0;
1250 
1251  /* Update image in the tree store */
1252  gtk_tree_store_set(store_look, &iter,
1253  LIST_ICON, (GdkPixbuf*) pixmaps[tmp->face]->icon_image,
1254  -1);
1255  }
1256  }
1257  valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store_look), &iter);
1258  }
1259 }
1260 
1267  animate_look();
1268 }
float weight
Definition: item.h:61
Animations animations[MAXANIM]
Definition: commands.c:1127
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: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:1057
GtkBuilder * window_xml
Definition: main.c:86
static void animate_look()
Definition: inventory.c:1206
int image_size
Definition: image.c:36
guint32 tick
Definition: client.c:61
static void draw_inv_table(int animate)
Definition: inventory.c:878
void command_show(const char *params)
Definition: inventory.c:589
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:132
static void setup_list_columns(GtkWidget *treeview)
Definition: inventory.c:336
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:677
static gboolean drawingarea_inventory_table_button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
Definition: inventory.c:833
#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:1135
static gboolean drawingarea_inventory_table_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
Definition: inventory.c:855
#define INV_SHOW_ITEM
Definition: inventory.c:79
int(* show_func)(item *it)
Definition: inventory.c:96
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: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:696
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:573
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:68
item * container
Definition: client.h:339
void draw_lists()
Definition: inventory.c:1075
void inventory_init(GtkWidget *window_root)
Definition: inventory.c:452
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:774
#define MAX_BUF
Definition: client.h:40
item_env
Definition: inventory.c:180
void set_weight_limit(guint32 wlim)
Definition: inventory.c:621
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:630
#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:1126
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:839
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:1108
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:281
Warning that something definitely didn'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:680
void inventory_get_styles()
Definition: inventory.c:425
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:212
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:581
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:674
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:1265
void draw_look_list()
Definition: inventory.c:739