1 /**********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <gtk/gtk.h>
23 
24 /* utility */
25 #include "fcintl.h"
26 #include "log.h"
27 
28 /* common */
29 #include "events.h"
30 #include "game.h"
31 #include "map.h"
32 #include "player.h"
33 
34 /* client */
35 #include "options.h"
36 
37 /* client/gui-gtk-2.0 */
38 #include "chatline.h"
39 #include "citydlg.h"
40 #include "gui_main.h"
41 #include "gui_stuff.h"
42 #include "mapview.h"
43 
44 #include "messagewin.h"
45 
46 
47 struct meswin_dialog {
48   struct gui_dialog *shell;
49   GtkTreeView *tree_view;
50 };
51 
52 /* Those values must match meswin_dialog_store_new(). */
53 enum meswin_columns {
54   MESWIN_COL_ICON,
55   MESWIN_COL_MESSAGE,
56 
57   /* Not visible. */
58   MESWIN_COL_WEIGHT,
59   MESWIN_COL_STYLE,
60   MESWIN_COL_ID,
61 
62   MESWIN_COL_NUM
63 };
64 
65 enum meswin_responses {
66   MESWIN_RES_GOTO = 1,
67   MESWIN_RES_POPUP_CITY
68 };
69 
70 static struct meswin_dialog meswin = { NULL, };
71 
72 /****************************************************************************
73   Create a tree model for the message window.
74 ****************************************************************************/
meswin_dialog_store_new(void)75 static GtkListStore *meswin_dialog_store_new(void)
76 {
77   return gtk_list_store_new(MESWIN_COL_NUM,
78                             GDK_TYPE_PIXBUF,    /* MESWIN_COL_ICON */
79                             G_TYPE_STRING,      /* MESWIN_COL_MESSAGE */
80                             G_TYPE_INT,         /* MESWIN_COL_WEIGHT */
81                             G_TYPE_INT,         /* MESWIN_COL_STYLE */
82                             G_TYPE_INT);        /* MESWIN_COL_ID */
83 }
84 
85 /****************************************************************************
86   Get the pango attributes for the visited state.
87 ****************************************************************************/
meswin_dialog_visited_get_attr(bool visited,gint * weight,gint * style)88 static void meswin_dialog_visited_get_attr(bool visited, gint *weight,
89                                            gint *style)
90 {
91   if (NULL != weight) {
92     *weight = (visited ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD);
93   }
94   if (NULL != style) {
95     *style = (visited ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
96   }
97 }
98 
99 /****************************************************************************
100   Set the visited state of the store.
101 ****************************************************************************/
meswin_dialog_set_visited(GtkTreeModel * model,GtkTreeIter * iter,bool visited)102 static void meswin_dialog_set_visited(GtkTreeModel *model,
103                                       GtkTreeIter *iter, bool visited)
104 {
105   gint row, weight, style;
106 
107   gtk_tree_model_get(model, iter, MESWIN_COL_ID, &row, -1);
108   meswin_dialog_visited_get_attr(visited, &weight, &style);
109   gtk_list_store_set(GTK_LIST_STORE(model), iter,
110                      MESWIN_COL_WEIGHT, weight,
111                      MESWIN_COL_STYLE, style,
112                      -1);
113   meswin_set_visited_state(row, visited);
114 }
115 
116 /****************************************************************************
117   Refresh a message window dialog.
118 ****************************************************************************/
meswin_dialog_refresh(struct meswin_dialog * pdialog)119 static void meswin_dialog_refresh(struct meswin_dialog *pdialog)
120 {
121   GtkTreeSelection *selection;
122   GtkTreeModel *model;
123   GtkListStore *store;
124   GtkTreeIter iter;
125   const struct message *pmsg;
126   gint weight, style;
127   int selected, i, num;
128   bool need_alert = FALSE;
129 
130   fc_assert_ret(NULL != pdialog);
131 
132   /* Save the selection. */
133   selection = gtk_tree_view_get_selection(pdialog->tree_view);
134   if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
135     gtk_tree_model_get(model, &iter, MESWIN_COL_ID, &selected, -1);
136   } else {
137     selected = -1;
138   }
139 
140   model = gtk_tree_view_get_model(pdialog->tree_view);
141   store = GTK_LIST_STORE(model);
142   num = meswin_get_num_messages();
143 
144   gtk_list_store_clear(store);
145   for (i = 0; i < num; i++) {
146     GdkPixbuf *pb;
147     struct sprite *icon;
148     int x0, y0, x1, y1, w, h;
149 
150     pmsg = meswin_get_message(i);
151 
152     if (gui_options.gui_gtk2_new_messages_go_to_top) {
153       gtk_list_store_prepend(store, &iter);
154     } else {
155       gtk_list_store_append(store, &iter);
156     }
157 
158     icon = get_event_sprite(tileset, pmsg->event);
159     sprite_get_bounding_box(icon, &x0, &y0, &x1, &y1);
160     w = (x1 - x0) + 1;
161     h = (y1 - y0) + 1;
162     pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, w, h);
163     gdk_pixbuf_copy_area(sprite_get_pixbuf(icon), x0, y0, w, h,
164                          pb, 0, 0);
165 
166     meswin_dialog_visited_get_attr(pmsg->visited, &weight, &style);
167     gtk_list_store_set(store, &iter,
168                        MESWIN_COL_ICON, pb,
169                        MESWIN_COL_MESSAGE, pmsg->descr,
170                        MESWIN_COL_WEIGHT, weight,
171                        MESWIN_COL_STYLE, style,
172                        MESWIN_COL_ID, i,
173                        -1);
174     g_object_unref(pb);
175     if (i == selected) {
176       /* Restore the selection. */
177       gtk_tree_selection_select_iter(selection, &iter);
178     }
179 
180     if (!pmsg->visited) {
181       need_alert = TRUE;
182     }
183   }
184 
185   if (need_alert) {
186     gui_dialog_alert(pdialog->shell);
187   }
188 }
189 
190 /**************************************************************************
191   Selection changed callback.
192 **************************************************************************/
meswin_dialog_selection_callback(GtkTreeSelection * selection,gpointer data)193 static void meswin_dialog_selection_callback(GtkTreeSelection *selection,
194                                              gpointer data)
195 {
196   struct meswin_dialog *pdialog = data;
197   const struct message *pmsg;
198   GtkTreeModel *model;
199   GtkTreeIter iter;
200   gint row;
201 
202   if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
203     return;
204   }
205 
206   gtk_tree_model_get(model, &iter, MESWIN_COL_ID, &row, -1);
207   pmsg = meswin_get_message(row);
208 
209   gui_dialog_set_response_sensitive(pdialog->shell, MESWIN_RES_GOTO,
210                                     NULL != pmsg && pmsg->location_ok);
211   gui_dialog_set_response_sensitive(pdialog->shell, MESWIN_RES_POPUP_CITY,
212                                     NULL != pmsg && pmsg->city_ok);
213 }
214 
215 /**************************************************************************
216   A row has been activated by the user.
217 **************************************************************************/
meswin_dialog_row_activated_callback(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * col,gpointer data)218 static void meswin_dialog_row_activated_callback(GtkTreeView *view,
219                                                  GtkTreePath *path,
220                                                  GtkTreeViewColumn *col,
221                                                  gpointer data)
222 {
223   GtkTreeModel *model = gtk_tree_view_get_model(view);
224   GtkTreeIter iter;
225   gint row;
226 
227   if (!gtk_tree_model_get_iter(model, &iter, path)) {
228     return;
229   }
230 
231   gtk_tree_model_get(model, &iter, MESWIN_COL_ID, &row, -1);
232 
233   if (NULL != meswin_get_message(row)) {
234     meswin_double_click(row);
235     meswin_dialog_set_visited(model, &iter, TRUE);
236   }
237 }
238 
239 /****************************************************************************
240   Mouse button press handler for the message window treeview. We only
241   care about right clicks on a row; this action centers on the tile
242   associated with the event at that row (if applicable).
243 ****************************************************************************/
meswin_dialog_button_press_callback(GtkWidget * widget,GdkEventButton * ev,gpointer data)244 static gboolean meswin_dialog_button_press_callback(GtkWidget *widget,
245                                                     GdkEventButton *ev,
246                                                     gpointer data)
247 {
248   GtkTreePath *path = NULL;
249   GtkTreeModel *model;
250   GtkTreeIter iter;
251   gint row;
252 
253   fc_assert_ret_val(GTK_IS_TREE_VIEW(widget), FALSE);
254 
255   if (GDK_BUTTON_PRESS  != ev->type || 3 != ev->button) {
256     return FALSE;
257   }
258 
259   if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), ev->x, ev->y,
260                                      &path, NULL, NULL, NULL)) {
261     return TRUE;
262   }
263 
264   model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
265   if (gtk_tree_model_get_iter(model, &iter, path)) {
266     gtk_tree_model_get(model, &iter, MESWIN_COL_ID, &row, -1);
267     meswin_goto(row);
268   }
269   gtk_tree_path_free(path);
270 
271   return TRUE;
272 }
273 
274 /**************************************************************************
275   Dialog response callback.
276 **************************************************************************/
meswin_dialog_response_callback(struct gui_dialog * pgui_dialog,int response,gpointer data)277 static void meswin_dialog_response_callback(struct gui_dialog *pgui_dialog,
278                                             int response, gpointer data)
279 {
280   struct meswin_dialog *pdialog = data;
281   GtkTreeSelection *selection;
282   GtkTreeModel *model;
283   GtkTreeIter iter;
284   gint row;
285 
286   switch (response) {
287   case MESWIN_RES_GOTO:
288   case MESWIN_RES_POPUP_CITY:
289     break;
290   default:
291     gui_dialog_destroy(pgui_dialog);
292     return;
293   }
294 
295   selection = gtk_tree_view_get_selection(pdialog->tree_view);
296   if (!gtk_tree_selection_get_selected(selection, &model, &iter)) {
297     return;
298   }
299 
300   gtk_tree_model_get(model, &iter, MESWIN_COL_ID, &row, -1);
301 
302   switch (response) {
303   case MESWIN_RES_GOTO:
304     meswin_goto(row);
305     break;
306   case MESWIN_RES_POPUP_CITY:
307     meswin_popup_city(row);
308     break;
309   }
310   meswin_dialog_set_visited(model, &iter, TRUE);
311 }
312 
313 /****************************************************************************
314   Initilialize a message window dialog.
315 ****************************************************************************/
meswin_dialog_init(struct meswin_dialog * pdialog)316 static void meswin_dialog_init(struct meswin_dialog *pdialog)
317 {
318   GtkWidget *view, *sw, *cmd, *notebook;
319   GtkBox *vbox;
320   GtkListStore *store;
321   GtkTreeSelection *selection;
322   GtkCellRenderer *renderer;
323   GtkTreeViewColumn *col;
324 
325   fc_assert_ret(NULL != pdialog);
326 
327   if (gui_options.gui_gtk2_message_chat_location == GUI_GTK_MSGCHAT_SPLIT) {
328     notebook = right_notebook;
329   } else {
330     notebook = bottom_notebook;
331   }
332 
333   gui_dialog_new(&pdialog->shell, GTK_NOTEBOOK(notebook), pdialog, TRUE);
334   gui_dialog_set_title(pdialog->shell, _("Messages"));
335   vbox = GTK_BOX(pdialog->shell->vbox);
336 
337   sw = gtk_scrolled_window_new(NULL, NULL);
338   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
339                                       GTK_SHADOW_ETCHED_IN);
340   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
341                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
342   gtk_box_pack_start(vbox, sw, TRUE, TRUE, 0);
343 
344   store = meswin_dialog_store_new();
345   view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
346   g_object_unref(store);
347   gtk_tree_view_columns_autosize(GTK_TREE_VIEW(view));
348   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
349   g_signal_connect(view, "row_activated",
350                    G_CALLBACK(meswin_dialog_row_activated_callback), NULL);
351   g_signal_connect(view, "button-press-event",
352                    G_CALLBACK(meswin_dialog_button_press_callback), NULL);
353   pdialog->tree_view = GTK_TREE_VIEW(view);
354 
355   renderer = gtk_cell_renderer_pixbuf_new();
356   col = gtk_tree_view_column_new_with_attributes(NULL, renderer,
357                                                  "pixbuf", MESWIN_COL_ICON, NULL);
358   gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
359   gtk_tree_view_column_set_visible(col, !gui_options.gui_gtk2_small_display_layout);
360 
361   renderer = gtk_cell_renderer_text_new();
362   col = gtk_tree_view_column_new_with_attributes(NULL, renderer,
363                                                  "text", MESWIN_COL_MESSAGE,
364                                                  "weight", MESWIN_COL_WEIGHT,
365                                                  "style", MESWIN_COL_STYLE,
366                                                  NULL);
367   gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
368   gtk_container_add(GTK_CONTAINER(sw), view);
369 
370   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
371   g_signal_connect(selection, "changed",
372                    G_CALLBACK(meswin_dialog_selection_callback), pdialog);
373 
374   if (gui_options.gui_gtk2_show_message_window_buttons) {
375     cmd = gui_dialog_add_stockbutton(pdialog->shell, GTK_STOCK_JUMP_TO,
376                                      _("Goto _Location"), MESWIN_RES_GOTO);
377     gtk_widget_set_sensitive(cmd, FALSE);
378 
379     cmd = gui_dialog_add_stockbutton(pdialog->shell, GTK_STOCK_ZOOM_IN,
380                                      _("I_nspect City"),
381                                      MESWIN_RES_POPUP_CITY);
382     gtk_widget_set_sensitive(cmd, FALSE);
383   }
384 
385   gui_dialog_add_button(pdialog->shell, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
386   gui_dialog_response_set_callback(pdialog->shell,
387                                    meswin_dialog_response_callback);
388   gui_dialog_set_default_size(pdialog->shell, 520, 300);
389 
390   meswin_dialog_refresh(pdialog);
391   gui_dialog_show_all(pdialog->shell);
392 }
393 
394 /****************************************************************************
395   Closes a message window dialog.
396 ****************************************************************************/
meswin_dialog_free(struct meswin_dialog * pdialog)397 static void meswin_dialog_free(struct meswin_dialog *pdialog)
398 {
399   fc_assert_ret(NULL != pdialog);
400 
401   gui_dialog_destroy(pdialog->shell);
402   fc_assert(NULL == pdialog->shell);
403 
404   memset(pdialog, 0, sizeof(*pdialog));
405 }
406 
407 /****************************************************************************
408   Popup the dialog inside the main-window, and optionally raise it.
409 ****************************************************************************/
meswin_dialog_popup(bool raise)410 void meswin_dialog_popup(bool raise)
411 {
412   if (NULL == meswin.shell) {
413     meswin_dialog_init(&meswin);
414   }
415 
416   gui_dialog_present(meswin.shell);
417   if (raise) {
418     gui_dialog_raise(meswin.shell);
419   }
420 }
421 
422 /****************************************************************************
423   Closes the message window dialog.
424 ****************************************************************************/
meswin_dialog_popdown(void)425 void meswin_dialog_popdown(void)
426 {
427   if (NULL != meswin.shell) {
428     meswin_dialog_free(&meswin);
429     fc_assert(NULL == meswin.shell);
430   }
431 }
432 
433 /****************************************************************************
434   Return TRUE iff the message window is open.
435 ****************************************************************************/
meswin_dialog_is_open(void)436 bool meswin_dialog_is_open(void)
437 {
438   return (NULL != meswin.shell);
439 }
440 
441 /****************************************************************************
442   Update the message window dialog.
443 ****************************************************************************/
real_meswin_dialog_update(void * unused)444 void real_meswin_dialog_update(void *unused)
445 {
446   if (NULL != meswin.shell) {
447     meswin_dialog_refresh(&meswin);
448   }
449 }
450