Crossfire Client, Trunk  R20996
spells.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 <assert.h>
22 #include <gtk/gtk.h>
23 
24 #include "image.h"
25 #include "metaserver.h"
26 #include "main.h"
27 #include "gtk2proto.h"
28 
29 enum Styles {
31 };
32 
33 static GtkListStore *spell_store;
34 static GtkTreeSelection *spell_selection;
35 static GtkWidget *spell_window, *spell_invoke,
38 
39 enum {
43 };
44 
45 static const char *Style_Names[Style_Last] = {
46  "spell_attuned", "spell_repelled", "spell_denied", "spell_normal"
47 };
49 static gpointer description_renderer = NULL;
52 static GtkStyle *spell_styles[Style_Last];
55 static int has_init = 0;
66 void spell_get_styles(void)
67 {
68  int i;
69  GtkStyle *tmp_style;
70  static int style_has_init=0;
71 
72  for (i=0; i < Style_Last; i++) {
73  if (style_has_init && spell_styles[i]) {
74  g_object_unref(spell_styles[i]);
75  }
76  tmp_style =
77  gtk_rc_get_style_by_paths(
78  gtk_settings_get_default(), NULL, Style_Names[i], G_TYPE_NONE);
79  if (tmp_style) {
80  spell_styles[i] = g_object_ref(tmp_style);
81  } else {
82  LOG(LOG_INFO, "spells.c::spell_get_styles",
83  "Unable to find style for %s", Style_Names[i]);
84  spell_styles[i] = NULL;
85  }
86  }
87  style_has_init = 1;
88 }
89 
100 static gboolean spell_selection_func(GtkTreeSelection *selection,
101  GtkTreeModel *model,
102  GtkTreePath *path,
103  gboolean path_currently_selected,
104  gpointer userdata)
105 {
106  gtk_widget_set_sensitive(spell_invoke, TRUE);
107  gtk_widget_set_sensitive(spell_cast, TRUE);
108  return TRUE;
109 }
110 
124 void on_spell_window_size_allocate(GtkWidget *widget, gpointer user_data)
125 {
126  guint i;
127  guint width;
128  gboolean valid;
129  GtkTreeIter iter;
130  guint column_count;
131  GList *column_list;
132  GtkTreeViewColumn *column;
133 
134  /* If the spell window has not been set up yet, do nothing. */
135  if (!has_init) {
136  return;
137  }
138  /*
139  * How wide is the spell window?
140  */
141  width = spell_treeview->allocation.width;
142  /*
143  * How many columns are in the spell window tree view?
144  */
145  column_list = gtk_tree_view_get_columns(GTK_TREE_VIEW(spell_treeview));
146  column_count = g_list_length(column_list);
147  /*
148  * Subtract the width of all but the last (Description) column from the
149  * total window width to figure out how much may be used for the final
150  * description column.
151  */
152  for (i = 0; i < column_count - 1; i += 1) {
153  column = g_list_nth_data(column_list, i);
154  width -= gtk_tree_view_column_get_width(column);
155  }
156  /*
157  * The column list allocated by gtk_tree_view_get_columns must be freed
158  * when it is no longer needed.
159  */
160  g_list_free(column_list);
161  /*
162  * Update the global variable used to configure the wrap-width for the
163  * spell dialog description column, then apply it to the cell renderer.
164  */
165  g_object_set(G_OBJECT(description_renderer), "wrap-width", width, NULL);
166  /*
167  * Traverse the spell store, and mark each row as changed. Get the first
168  * row, mark it, and then process the rest of the rows (if there are any).
169  * This re-flows the spell descriptions to the new wrap-width, and adjusts
170  * the height of each row as needed to optimize the vertical space used.
171  */
172  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(spell_store), &iter);
173  while (valid) {
174  GtkTreePath *tree_path;
175 
176  tree_path =
177  gtk_tree_model_get_path(GTK_TREE_MODEL(spell_store), &iter);
178  gtk_tree_model_row_changed(
179  GTK_TREE_MODEL(spell_store), tree_path, &iter);
180  gtk_tree_path_free(tree_path);
181  valid =
182  gtk_tree_model_iter_next(GTK_TREE_MODEL(spell_store), &iter);
183  }
184 }
185 
191 {
192  int i;
193  Spell *spell;
194  GtkTreeIter iter;
195  char buf[MAX_BUF];
196  GtkStyle *row_style;
197  GdkColor *foreground=NULL;
198  GdkColor *background=NULL;
199  PangoFontDescription *font=NULL;
200 
201  /* If the window/spellstore hasn't been created, return. */
202  if (!has_init) {
203  return;
204  }
205 
206  cpl.spells_updated = 0;
207 
208  /* We could try to do this in spell_get_styles, but if the window isn't
209  * active, it won't work. This is called whenever the window is made
210  * active, so we know it will work, and the time to set this info here,
211  * even though it may not change often, is pretty trivial.
212  */
213  for (i=0; i < Style_Last; i++) {
214  if (spell_styles[i]) {
215  gtk_widget_modify_fg(spell_label[i],
216  GTK_STATE_NORMAL, &spell_styles[i]->text[GTK_STATE_NORMAL]);
217  gtk_widget_modify_font(spell_label[i], spell_styles[i]->font_desc);
218  gtk_widget_modify_bg(spell_eventbox[i],
219  GTK_STATE_NORMAL, &spell_styles[i]->base[GTK_STATE_NORMAL]);
220  } else {
221  gtk_widget_modify_fg(spell_label[i],GTK_STATE_NORMAL, NULL);
222  gtk_widget_modify_font(spell_label[i], NULL);
223  gtk_widget_modify_bg(spell_eventbox[i],GTK_STATE_NORMAL, NULL);
224  }
225  }
226 
227  gtk_list_store_clear(spell_store);
228  for (spell = cpl.spelldata; spell; spell=spell->next) {
229  gtk_list_store_append(spell_store, &iter);
230 
231  buf[0] = 0;
232  if (spell->sp) {
233  snprintf(buf, sizeof(buf), "%d Mana ", spell->sp);
234  }
235  if (spell->grace)
236  snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
237  "%d Grace", spell->grace);
238 
239  if (spell->path & cpl.stats.denied) {
240  row_style = spell_styles[Style_Denied];
241  } else if (spell->path & cpl.stats.repelled) {
242  row_style = spell_styles[Style_Repelled];
243  } else if (spell->path & cpl.stats.attuned) {
244  row_style = spell_styles[Style_Attuned];
245  } else {
246  row_style = spell_styles[Style_Normal];
247  }
248 
249  if (row_style) {
250  foreground = &row_style->text[GTK_STATE_NORMAL];
251  background = &row_style->base[GTK_STATE_NORMAL];
252  font = row_style->font_desc;
253  } else {
254  foreground=NULL;
255  background=NULL;
256  font=NULL;
257  }
258 
259  gtk_list_store_set(
260  spell_store, &iter,
261  LIST_NAME, spell->name,
262  LIST_LEVEL, spell->level,
263  LIST_COST, buf,
264  LIST_DAMAGE, spell->dam,
265  LIST_SKILL, spell->skill,
266  LIST_DESCRIPTION, spell->message,
267  LIST_BACKGROUND, background,
268  LIST_FOREGROUND, foreground,
269  LIST_FONT, font,
270  LIST_MAX_SP, (spell->sp > spell->grace) ? spell->sp : spell->grace,
271  LIST_TAG, spell->tag,
272  -1
273  );
274  }
275 }
276 
282 void on_spells_activate(GtkMenuItem *menuitem, gpointer user_data) {
283  GtkWidget *widget;
284 
285  if (!has_init) {
286  GtkCellRenderer *renderer;
287  GtkTreeViewColumn *column;
288 
289  spell_window = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_window"));
290 
291  spell_invoke = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_invoke"));
292  spell_cast = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_cast"));
293  spell_options = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_options"));
294  spell_treeview = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_treeview"));
295 
296  g_signal_connect((gpointer) spell_window, "size-allocate",
297  G_CALLBACK(on_spell_window_size_allocate), NULL);
298  g_signal_connect((gpointer) spell_window, "delete-event",
299  G_CALLBACK(gtk_widget_hide_on_delete), NULL);
300  g_signal_connect((gpointer) spell_treeview, "row_activated",
301  G_CALLBACK(on_spell_treeview_row_activated), NULL);
302  g_signal_connect((gpointer) spell_cast, "clicked",
303  G_CALLBACK(on_spell_cast_clicked), NULL);
304  g_signal_connect((gpointer) spell_invoke, "clicked",
305  G_CALLBACK(on_spell_invoke_clicked), NULL);
306 
307  widget = GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_close"));
308  g_signal_connect((gpointer) widget, "clicked",
309  G_CALLBACK(on_spell_close_clicked), NULL);
310 
311  spell_store =
312  gtk_list_store_new(
313  14,
314  G_TYPE_OBJECT, /* Image - not used */
315  G_TYPE_STRING, /* Name */
316  G_TYPE_INT, /* Level */
317  G_TYPE_INT, /* Time */
318  G_TYPE_STRING, /* SP/Grace */
319  G_TYPE_INT, /* Damage */
320  G_TYPE_STRING, /* Skill name */
321  G_TYPE_INT, /* Spell path */
322  G_TYPE_STRING, /* Description */
323  GDK_TYPE_COLOR, /* Background color of the entry */
324  G_TYPE_INT,
325  G_TYPE_INT,
326  GDK_TYPE_COLOR,
327  PANGO_TYPE_FONT_DESCRIPTION
328  );
329 
330  gtk_tree_view_set_model(
331  GTK_TREE_VIEW(spell_treeview), GTK_TREE_MODEL(spell_store));
332  gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(spell_treeview), TRUE);
333 
334  /* Note: it is intentional we don't show (render) some fields:
335  * image - we don't have images right now it seems.
336  * time - not sure if it worth the space.
337  * spell path - done by color
338  *
339  * Note: Cell alignment is set to top right instead of the default,
340  * to improve readability when descriptions wrap to multiple lines.
341  */
342  renderer = gtk_cell_renderer_text_new();
343  renderer->xalign = 0;
344  renderer->yalign = 0;
345  column = gtk_tree_view_column_new_with_attributes(
346  "Spell", renderer, "text", LIST_NAME, NULL);
347  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
348  gtk_tree_view_column_set_sort_column_id(column, LIST_NAME);
349  gtk_tree_view_column_add_attribute(
350  column, renderer, "background-gdk", LIST_BACKGROUND);
351  gtk_tree_view_column_add_attribute(
352  column, renderer, "foreground-gdk", LIST_FOREGROUND);
353  gtk_tree_view_column_add_attribute(
354  column, renderer, "font-desc", LIST_FONT);
355 
356  renderer = gtk_cell_renderer_text_new();
357  renderer->xalign = 0.4;
358  renderer->yalign = 0;
359  column = gtk_tree_view_column_new_with_attributes(
360  "Level", renderer, "text", LIST_LEVEL, NULL);
361  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
362  gtk_tree_view_column_set_sort_column_id(column, LIST_LEVEL);
363  gtk_tree_view_column_add_attribute(
364  column, renderer, "background-gdk", LIST_BACKGROUND);
365  gtk_tree_view_column_add_attribute(
366  column, renderer, "foreground-gdk", LIST_FOREGROUND);
367  gtk_tree_view_column_add_attribute(
368  column, renderer, "font-desc", LIST_FONT);
369 
370  renderer = gtk_cell_renderer_text_new();
371  renderer->xalign = 0.4;
372  renderer->yalign = 0;
373  column = gtk_tree_view_column_new_with_attributes(
374  "Cost/Cast", renderer, "text", LIST_COST, NULL);
375  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
376 
377  /* Since this is a string column, it would do a string sort. Instead,
378  * we set up a int column and tie this column to sort on that.
379  */
380  gtk_tree_view_column_set_sort_column_id(column, LIST_MAX_SP);
381  gtk_tree_view_column_add_attribute(
382  column, renderer, "background-gdk", LIST_BACKGROUND);
383  gtk_tree_view_column_add_attribute(
384  column, renderer, "foreground-gdk", LIST_FOREGROUND);
385  gtk_tree_view_column_add_attribute(
386  column, renderer, "font-desc", LIST_FONT);
387 
388  renderer = gtk_cell_renderer_text_new();
389  renderer->xalign = 0.4;
390  renderer->yalign = 0;
391  column = gtk_tree_view_column_new_with_attributes(
392  "Damage", renderer, "text", LIST_DAMAGE, NULL);
393  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
394  gtk_tree_view_column_set_sort_column_id(column, LIST_DAMAGE);
395  gtk_tree_view_column_add_attribute(
396  column, renderer, "background-gdk", LIST_BACKGROUND);
397  gtk_tree_view_column_add_attribute(
398  column, renderer, "foreground-gdk", LIST_FOREGROUND);
399  gtk_tree_view_column_add_attribute(
400  column, renderer, "font-desc", LIST_FONT);
401 
402  column = gtk_tree_view_column_new_with_attributes(
403  "Skill", renderer, "text", LIST_SKILL, NULL);
404  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
405  gtk_tree_view_column_set_sort_column_id(column, LIST_SKILL);
406  gtk_tree_view_column_add_attribute(
407  column, renderer, "background-gdk", LIST_BACKGROUND);
408  gtk_tree_view_column_add_attribute(
409  column, renderer, "foreground-gdk", LIST_FOREGROUND);
410  gtk_tree_view_column_add_attribute(
411  column, renderer, "font-desc", LIST_FONT);
412 
413  renderer = gtk_cell_renderer_text_new();
414  renderer->xalign = 0;
415  renderer->yalign = 0;
416  column = gtk_tree_view_column_new_with_attributes(
417  "Description", renderer, "text", LIST_DESCRIPTION, NULL);
418  gtk_tree_view_append_column(GTK_TREE_VIEW(spell_treeview), column);
419  gtk_tree_view_column_add_attribute(
420  column, renderer, "background-gdk", LIST_BACKGROUND);
421  gtk_tree_view_column_add_attribute(
422  column, renderer, "foreground-gdk", LIST_FOREGROUND);
423  gtk_tree_view_column_add_attribute(
424  column, renderer, "font-desc", LIST_FONT);
425  /*
426  * Set up the description column so it wraps lengthy descriptions over
427  * multiple lines and at word boundaries. A default wrap-width is
428  * applied to constrain the column width to a reasonable value. The
429  * actual value used here is somewhat unimportant since a corrected
430  * width is computed and applied later, but, it does approximate the
431  * column size that is appropriate for the dialog's default width.
432  */
433  g_object_set(G_OBJECT(renderer),
434  "wrap-width", 300, "wrap-mode", PANGO_WRAP_WORD, NULL);
435  /*
436  * Preserve the description text cell renderer pointer to facilitate
437  * setting the wrap-width relative to the dialog's size and content.
438  */
439  description_renderer = renderer;
440 
442  gtk_tree_view_get_selection(GTK_TREE_VIEW(spell_treeview));
443  gtk_tree_selection_set_mode(spell_selection, GTK_SELECTION_BROWSE);
444  gtk_tree_selection_set_select_function(
446 
447  gtk_tree_sortable_set_sort_column_id(
448  GTK_TREE_SORTABLE(spell_store), LIST_NAME, GTK_SORT_ASCENDING);
449 
450  /* The style code will set the colors for these */
452  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_label_attuned"));
454  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_label_repelled"));
456  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_label_denied"));
458  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_label_normal"));
459 
460  /* We use eventboxes because the label widget is a transparent widget.
461  * We can't set the background in it and have it work. But we can set
462  * the background in the event box, and put the label widget in the
463  * eventbox.
464  */
466  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_eventbox_attuned"));
468  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_eventbox_repelled"));
470  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_eventbox_denied"));
472  GTK_WIDGET(gtk_builder_get_object(dialog_xml, "spell_eventbox_normal"));
473  }
474  gtk_widget_set_sensitive(spell_invoke, FALSE);
475  gtk_widget_set_sensitive(spell_cast, FALSE);
476  gtk_widget_show(spell_window);
478 
479  has_init = 1;
480 
481  /* Must be called after has_init is set to 1 */
483 }
484 
492 void on_spell_treeview_row_activated(GtkTreeView *treeview,
493  GtkTreePath *path,
494  GtkTreeViewColumn *column,
495  gpointer user_data)
496 {
497  int tag;
498  char command[MAX_BUF];
499  const char *options=NULL;
500  GtkTreeIter iter;
501  GtkTreeModel *model;
502 
503  model = gtk_tree_view_get_model(treeview);
504  if (gtk_tree_model_get_iter(model, &iter, path)) {
505  gtk_tree_model_get(model, &iter, LIST_TAG, &tag, -1);
506 
507  if (!tag) {
508  LOG(LOG_ERROR, "spells.c::on_spell_cast_clicked",
509  "Unable to get spell tag\n");
510  return;
511  }
512 
513  assert(options != NULL);
514  snprintf(command, MAX_BUF-1, "cast %d %s", tag, options);
515  send_command(command, -1, 1);
516  }
517 }
518 
524 void on_spell_cast_clicked(GtkButton *button, gpointer user_data)
525 {
526  int tag;
527  char command[MAX_BUF];
528  const char *options = NULL;
529  GtkTreeIter iter;
530  GtkTreeModel *model;
531 
532  options = gtk_entry_get_text(GTK_ENTRY(spell_options));
533 
534  if (gtk_tree_selection_get_selected(spell_selection, &model, &iter)) {
535  gtk_tree_model_get(model, &iter, LIST_TAG, &tag, -1);
536 
537  if (!tag) {
538  LOG(LOG_ERROR, "spells.c::on_spell_cast_clicked",
539  "Unable to get spell tag\n");
540  return;
541  }
542  snprintf(command, MAX_BUF-1, "cast %d %s", tag, options);
543  send_command(command, -1, 1);
544  }
545 }
546 
552 void on_spell_invoke_clicked(GtkButton *button, gpointer user_data)
553 {
554  int tag;
555  char command[MAX_BUF];
556  const char *options=NULL;
557  GtkTreeIter iter;
558  GtkTreeModel *model;
559 
560  options = gtk_entry_get_text(GTK_ENTRY(spell_options));
561 
562  if (gtk_tree_selection_get_selected(spell_selection, &model, &iter)) {
563  gtk_tree_model_get(model, &iter, LIST_TAG, &tag, -1);
564 
565  if (!tag) {
566  LOG(LOG_ERROR, "spells.c::on_spell_invoke_clicked",
567  "Unable to get spell tag\n");
568  return;
569  }
570  snprintf(command, MAX_BUF-1, "invoke %d %s", tag, options);
571  send_command(command, -1, 1);
572  }
573 }
574 
580 void on_spell_close_clicked(GtkButton *button, gpointer user_data)
581 {
582  gtk_widget_hide(spell_window);
583 }
584 
void on_spells_activate(GtkMenuItem *menuitem, gpointer user_data)
Definition: spells.c:282
static GtkWidget * spell_label[Style_Last]
Definition: spells.c:35
guint16 level
Definition: client.h:301
static const char * Style_Names[Style_Last]
Definition: spells.c:45
static GtkWidget * spell_eventbox[Style_Last]
Definition: spells.c:35
void on_spell_cast_clicked(GtkButton *button, gpointer user_data)
Definition: spells.c:524
guint16 dam
Definition: client.h:305
guint16 sp
Definition: client.h:303
void on_spell_treeview_row_activated(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data)
Definition: spells.c:492
static gpointer description_renderer
Definition: spells.c:49
Stats stats
Definition: client.h:349
static GtkWidget * spell_cast
Definition: spells.c:35
static GOptionEntry options[]
Definition: main.c:66
guint32 path
Definition: client.h:315
static int width
Definition: mapdata.c:79
void LOG(LogLevel level, const char *origin, const char *format,...)
Definition: misc.c:111
guint32 repelled
Definition: client.h:282
static GtkTreeSelection * spell_selection
Definition: spells.c:34
void on_spell_close_clicked(GtkButton *button, gpointer user_data)
Definition: spells.c:580
guint16 grace
Definition: client.h:304
void on_spell_window_size_allocate(GtkWidget *widget, gpointer user_data)
Definition: spells.c:124
int send_command(const char *command, int repeat, int must_send)
Definition: player.c:156
void spell_get_styles(void)
Definition: spells.c:66
void on_spell_invoke_clicked(GtkButton *button, gpointer user_data)
Definition: spells.c:552
Client_Player cpl
Definition: client.c:66
char message[10000]
Definition: client.h:297
#define MAX_BUF
Definition: client.h:40
static GtkWidget * spell_window
Definition: spells.c:35
guint32 tag
Definition: client.h:299
struct Spell_struct * next
Definition: client.h:295
Warning that something definitely didn&#39;t work.
Definition: client.h:444
Spell * spelldata
Definition: client.h:350
guint32 spells_updated
Definition: client.h:353
char name[256]
Definition: client.h:296
static GtkWidget * spell_treeview
Definition: spells.c:35
Styles
Definition: inventory.c:62
static int has_init
Definition: spells.c:55
void update_spell_information(void)
Definition: spells.c:190
static gboolean spell_selection_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer userdata)
Definition: spells.c:100
static GtkListStore * spell_store
Definition: spells.c:33
static GtkStyle * spell_styles[Style_Last]
Definition: spells.c:52
GtkBuilder * dialog_xml
Definition: main.c:96
Minor, non-harmful issues.
Definition: client.h:442
guint32 attuned
Definition: client.h:279
static GtkWidget * spell_invoke
Definition: spells.c:35
char * skill
Definition: client.h:313
guint32 denied
Definition: client.h:285
static GtkWidget * spell_options
Definition: spells.c:35