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 <gtk/gtk.h>
19 
20 /* utility */
21 #include "fcintl.h"
22 #include "support.h"
23 
24 /* common */
25 #include "combat.h"
26 #include "game.h"
27 #include "map.h"
28 #include "player.h"
29 #include "unit.h"
30 
31 #include "overview_common.h"
32 
33 /* client */
34 #include "client_main.h"
35 #include "climap.h"
36 #include "climisc.h"
37 #include "control.h"
38 #include "editor.h"
39 #include "tilespec.h"
40 #include "text.h"
41 
42 /* client/agents */
43 #include "cma_core.h"
44 
45 /* client/gui-gtk-3.0 */
46 #include "chatline.h"
47 #include "citydlg.h"
48 #include "colors.h"
49 #include "dialogs.h"
50 #include "editgui.h"
51 #include "graphics.h"
52 #include "gui_main.h"
53 #include "inputdlg.h"
54 #include "mapview.h"
55 #include "menu.h"
56 
57 #include "mapctrl.h"
58 
59 struct tmousepos { int x, y; };
60 extern gint cur_x, cur_y;
61 
62 /**************************************************************************
63   Button released when showing info label
64 **************************************************************************/
popit_button_release(GtkWidget * w,GdkEventButton * event)65 static gboolean popit_button_release(GtkWidget *w, GdkEventButton *event)
66 {
67   gtk_grab_remove(w);
68   gdk_device_ungrab(event->device, event->time);
69   gtk_widget_destroy(w);
70   return FALSE;
71 }
72 
73 /**************************************************************************
74   Put the popup on a smart position, after the real size of the widget is
75   known: left of the cursor if within the right half of the map, and vice
76   versa; displace the popup so as not to obscure it by the mouse cursor;
77   stay always within the map if possible.
78 **************************************************************************/
popupinfo_positioning_callback(GtkWidget * w,GtkAllocation * alloc,gpointer data)79 static void popupinfo_positioning_callback(GtkWidget *w, GtkAllocation *alloc,
80 					   gpointer data)
81 {
82   struct tmousepos *mousepos = data;
83   float x, y;
84   struct tile *ptile;
85 
86   ptile = canvas_pos_to_tile(mousepos->x, mousepos->y);
87   if (tile_to_canvas_pos(&x, &y, ptile)) {
88     gint minx, miny, maxy;
89 
90     gdk_window_get_origin(gtk_widget_get_window(map_canvas), &minx, &miny);
91     maxy = miny + gtk_widget_get_allocated_height(map_canvas);
92 
93     if (x > mapview.width/2) {
94       /* right part of the map */
95       x += minx;
96       y += miny + (tileset_tile_height(tileset) - alloc->height)/2;
97 
98       y = CLIP(miny, y, maxy - alloc->height);
99 
100       gtk_window_move(GTK_WINDOW(w), x - alloc->width, y);
101     } else {
102       /* left part of the map */
103       x += minx + tileset_tile_width(tileset);
104       y += miny + (tileset_tile_height(tileset) - alloc->height)/2;
105 
106       y = CLIP(miny, y, maxy - alloc->height);
107 
108       gtk_window_move(GTK_WINDOW(w), x, y);
109     }
110   }
111 }
112 
113 /**************************************************************************
114   Popup a label with information about the tile, unit, city, when the user
115   used the middle mouse button on the map.
116 **************************************************************************/
popit(GdkEventButton * event,struct tile * ptile)117 static void popit(GdkEventButton *event, struct tile *ptile)
118 {
119   GtkWidget *p;
120   static struct tmousepos mousepos;
121   struct unit *punit;
122 
123   if (TILE_UNKNOWN != client_tile_get_known(ptile)) {
124     p = gtk_window_new(GTK_WINDOW_POPUP);
125     gtk_container_set_border_width(GTK_CONTAINER(p), 4);
126     gtk_window_set_transient_for(GTK_WINDOW(p), GTK_WINDOW(toplevel));
127     gtk_container_add(GTK_CONTAINER(p), gtk_label_new(popup_info_text(ptile)));
128 
129     punit = find_visible_unit(ptile);
130 
131     if (punit) {
132       mapdeco_set_gotoroute(punit);
133       if (punit->goto_tile) {
134         mapdeco_set_crosshair(punit->goto_tile, TRUE);
135       }
136     }
137     mapdeco_set_crosshair(ptile, TRUE);
138 
139     g_signal_connect(p, "destroy",
140 		     G_CALLBACK(popupinfo_popdown_callback), NULL);
141 
142     mousepos.x = event->x;
143     mousepos.y = event->y;
144 
145     g_signal_connect(p, "size-allocate",
146 		     G_CALLBACK(popupinfo_positioning_callback),
147 		     &mousepos);
148 
149     gtk_widget_show_all(p);
150     gdk_device_grab(event->device, gtk_widget_get_window(p),
151                     GDK_OWNERSHIP_NONE, TRUE, GDK_BUTTON_RELEASE_MASK, NULL,
152                     event->time);
153     gtk_grab_add(p);
154 
155     g_signal_connect_after(p, "button_release_event",
156                            G_CALLBACK(popit_button_release), NULL);
157   }
158 }
159 
160 /**************************************************************************
161   Information label destruction requested
162 **************************************************************************/
popupinfo_popdown_callback(GtkWidget * w,gpointer data)163 void popupinfo_popdown_callback(GtkWidget *w, gpointer data)
164 {
165   mapdeco_clear_crosshairs();
166   mapdeco_clear_gotoroutes();
167 }
168 
169 /**************************************************************************
170   Callback from city name dialog for new city.
171 **************************************************************************/
name_new_city_popup_callback(gpointer data,gint response,const char * input)172 static void name_new_city_popup_callback(gpointer data, gint response,
173                                          const char *input)
174 {
175   int idx = GPOINTER_TO_INT(data);
176 
177   switch (response) {
178   case GTK_RESPONSE_OK:
179     finish_city(index_to_tile(idx), input);
180     break;
181   case GTK_RESPONSE_CANCEL:
182   case GTK_RESPONSE_DELETE_EVENT:
183     cancel_city(index_to_tile(idx));
184     break;
185   }
186 }
187 
188 /**************************************************************************
189  Popup dialog where the user choose the name of the new city
190  punit = (settler) unit which builds the city
191  suggestname = suggetion of the new city's name
192 **************************************************************************/
popup_newcity_dialog(struct unit * punit,const char * suggestname)193 void popup_newcity_dialog(struct unit *punit, const char *suggestname)
194 {
195   input_dialog_create(GTK_WINDOW(toplevel), /*"shellnewcityname" */
196                       _("Build New City"),
197                       _("What should we call our new city?"), suggestname,
198                       name_new_city_popup_callback,
199                       GINT_TO_POINTER(tile_index(unit_tile(punit))));
200 }
201 
202 /**************************************************************************
203  Enable or disable the turn done button.
204  Should probably some where else.
205 **************************************************************************/
set_turn_done_button_state(bool state)206 void set_turn_done_button_state(bool state)
207 {
208   gtk_widget_set_sensitive(turn_done_button, state);
209 }
210 
211 /**************************************************************************
212  Handle 'Mouse button released'. Because of the quickselect feature,
213  the release of both left and right mousebutton can launch the goto.
214 **************************************************************************/
butt_release_mapcanvas(GtkWidget * w,GdkEventButton * ev,gpointer data)215 gboolean butt_release_mapcanvas(GtkWidget *w, GdkEventButton *ev, gpointer data)
216 {
217   if (editor_is_active()) {
218     return handle_edit_mouse_button_release(ev);
219   }
220 
221   if (ev->button == 1 || ev->button == 3) {
222     release_goto_button(ev->x, ev->y);
223   }
224   if (ev->button == 3 && (rbutton_down || hover_state != HOVER_NONE))  {
225     release_right_button(ev->x, ev->y,
226                          (ev->state & GDK_SHIFT_MASK) != 0);
227   }
228 
229   return TRUE;
230 }
231 
232 /**************************************************************************
233  Handle all mouse button press on canvas.
234  Future feature: User-configurable mouse clicks.
235 **************************************************************************/
butt_down_mapcanvas(GtkWidget * w,GdkEventButton * ev,gpointer data)236 gboolean butt_down_mapcanvas(GtkWidget *w, GdkEventButton *ev, gpointer data)
237 {
238   struct city *pcity = NULL;
239   struct tile *ptile = NULL;
240 
241   if (editor_is_active()) {
242     return handle_edit_mouse_button_press(ev);
243   }
244 
245   if (!can_client_change_view()) {
246     return TRUE;
247   }
248 
249   gtk_widget_grab_focus(map_canvas);
250   ptile = canvas_pos_to_tile(ev->x, ev->y);
251   pcity = ptile ? tile_city(ptile) : NULL;
252 
253   switch (ev->button) {
254 
255   case 1: /* LEFT mouse button */
256 
257     /* <SHIFT> + <CONTROL> + LMB : Adjust workers. */
258     if ((ev->state & GDK_SHIFT_MASK) && (ev->state & GDK_CONTROL_MASK)) {
259       adjust_workers_button_pressed(ev->x, ev->y);
260     }
261     /* <CONTROL> + LMB : Quickselect a sea unit. */
262     else if (ev->state & GDK_CONTROL_MASK) {
263       action_button_pressed(ev->x, ev->y, SELECT_SEA);
264     }
265     /* <SHIFT> + LMB: Append focus unit. */
266     else if (ptile && (ev->state & GDK_SHIFT_MASK)) {
267       action_button_pressed(ev->x, ev->y, SELECT_APPEND);
268     }
269     /* <ALT> + LMB: popit (same as middle-click) */
270     /* FIXME: we need a general mechanism for letting freeciv work with
271      * 1- or 2-button mice. */
272     else if (ptile && (ev->state & GDK_MOD1_MASK)) {
273       popit(ev, ptile);
274     }
275     /* LMB in Area Selection mode. */
276     else if(tiles_hilited_cities) {
277       if (ptile) {
278         toggle_tile_hilite(ptile);
279       }
280     }
281     /* Plain LMB click. */
282     else {
283       action_button_pressed(ev->x, ev->y, SELECT_POPUP);
284     }
285     break;
286 
287   case 2: /* MIDDLE mouse button */
288 
289     /* <CONTROL> + MMB: Wake up sentries. */
290     if (ev->state & GDK_CONTROL_MASK) {
291       wakeup_button_pressed(ev->x, ev->y);
292     }
293     /* Plain Middle click. */
294     else if (ptile) {
295       popit(ev, ptile);
296     }
297     break;
298 
299   case 3: /* RIGHT mouse button */
300 
301     /* <CONTROL> + <ALT> + RMB : insert city or tile chat link. */
302     /* <CONTROL> + <ALT> + <SHIFT> + RMB : insert unit chat link. */
303     if (ptile && (ev->state & GDK_MOD1_MASK)
304         && (ev->state & GDK_CONTROL_MASK)) {
305       inputline_make_chat_link(ptile, (ev->state & GDK_SHIFT_MASK) != 0);
306     }
307     /* <SHIFT> + <ALT> + RMB : Show/hide workers. */
308     else if ((ev->state & GDK_SHIFT_MASK) && (ev->state & GDK_MOD1_MASK)) {
309       key_city_overlay(ev->x, ev->y);
310     }
311     /* <SHIFT + CONTROL> + RMB: Paste Production. */
312     else if ((ev->state & GDK_SHIFT_MASK) && (ev->state & GDK_CONTROL_MASK)
313              && pcity != NULL) {
314       clipboard_paste_production(pcity);
315       cancel_tile_hiliting();
316     }
317     /* <SHIFT> + RMB on city/unit: Copy Production. */
318     /* If nothing to copy, fall through to rectangle selection. */
319     else if (ev->state & GDK_SHIFT_MASK
320              && clipboard_copy_production(ptile)) {
321       /* Already done the copy */
322     }
323     /* <CONTROL> + RMB : Quickselect a land unit. */
324     else if (ev->state & GDK_CONTROL_MASK) {
325       action_button_pressed(ev->x, ev->y, SELECT_LAND);
326     }
327     /* Plain RMB click. Area selection. */
328     else {
329       /*  A foolproof user will depress button on canvas,
330        *  release it on another widget, and return to canvas
331        *  to find rectangle still active.
332        */
333       if (rectangle_active) {
334         release_right_button(ev->x, ev->y,
335                              (ev->state & GDK_SHIFT_MASK) != 0);
336         return TRUE;
337       }
338       if (hover_state == HOVER_NONE) {
339         anchor_selection_rectangle(ev->x, ev->y);
340         rbutton_down = TRUE; /* causes rectangle updates */
341       }
342     }
343     break;
344 
345   default:
346     break;
347   }
348 
349 
350   return TRUE;
351 }
352 
353 /**************************************************************************
354   Update goto line so that destination is at current mouse pointer location.
355 **************************************************************************/
create_line_at_mouse_pos(void)356 void create_line_at_mouse_pos(void)
357 {
358   int x, y;
359   GdkWindow *window;
360   GdkDeviceManager *manager =
361       gdk_display_get_device_manager(gtk_widget_get_display(toplevel));
362   GdkDevice *pointer = gdk_device_manager_get_client_pointer(manager);
363 
364   if (!pointer) {
365     return;
366   }
367 
368   window = gdk_device_get_window_at_position(pointer, &x, &y);
369   if (window) {
370     if (window == gtk_widget_get_window(map_canvas)) {
371       update_line(x, y);
372     } else if (window == gtk_widget_get_window(overview_canvas)) {
373       overview_update_line(x, y);
374     }
375   }
376 }
377 
378 /**************************************************************************
379  The Area Selection rectangle. Called by center_tile_mapcanvas() and
380  when the mouse pointer moves.
381 **************************************************************************/
update_rect_at_mouse_pos(void)382 void update_rect_at_mouse_pos(void)
383 {
384   int x, y;
385   GdkWindow *window;
386   GdkDevice *pointer;
387   GdkModifierType mask;
388   GdkDeviceManager *manager =
389       gdk_display_get_device_manager(gtk_widget_get_display(toplevel));
390 
391   pointer = gdk_device_manager_get_client_pointer(manager);
392   if (!pointer) {
393     return;
394   }
395 
396   window = gdk_device_get_window_at_position(pointer, &x, &y);
397   if (window && window == gtk_widget_get_window(map_canvas)) {
398     gdk_device_get_state(pointer, window, NULL, &mask);
399     if (mask & GDK_BUTTON3_MASK) {
400       update_selection_rectangle(x, y);
401     }
402   }
403 }
404 
405 /**************************************************************************
406   Triggered by the mouse moving on the mapcanvas, this function will
407   update the mouse cursor and goto lines.
408 **************************************************************************/
move_mapcanvas(GtkWidget * w,GdkEventMotion * ev,gpointer data)409 gboolean move_mapcanvas(GtkWidget *w, GdkEventMotion *ev, gpointer data)
410 {
411   if (gui_options.gui_gtk3_mouse_over_map_focus
412       && !gtk_widget_has_focus(map_canvas)) {
413     gtk_widget_grab_focus(map_canvas);
414   }
415 
416   if (editor_is_active()) {
417     return handle_edit_mouse_move(ev);
418   }
419 
420   cur_x = ev->x;
421   cur_y = ev->y;
422   update_line(ev->x, ev->y);
423   if (rbutton_down && (ev->state & GDK_BUTTON3_MASK)) {
424     update_selection_rectangle(ev->x, ev->y);
425   }
426 
427   if (keyboardless_goto_button_down && hover_state == HOVER_NONE) {
428     maybe_activate_keyboardless_goto(ev->x, ev->y);
429   }
430   control_mouse_cursor(canvas_pos_to_tile(ev->x, ev->y));
431 
432   return TRUE;
433 }
434 
435 /**************************************************************************
436   This function will reset the mouse cursor if it leaves the map.
437 **************************************************************************/
leave_mapcanvas(GtkWidget * widget,GdkEventCrossing * event)438 gboolean leave_mapcanvas(GtkWidget *widget, GdkEventCrossing *event)
439 {
440   if (gtk_notebook_get_current_page(GTK_NOTEBOOK(top_notebook))
441       != gtk_notebook_page_num(GTK_NOTEBOOK(top_notebook), map_widget)) {
442     /* Map is not currently topmost tab. Do not use tile specific cursors. */
443     update_mouse_cursor(CURSOR_DEFAULT);
444     return TRUE;
445   }
446 
447   /* Bizarrely, this function can be called even when we don't "leave"
448    * the map canvas, for instance, it gets called any time the mouse is
449    * clicked. */
450   if (map_exists()
451       && event->x >= 0 && event->y >= 0
452       && event->x < mapview.width && event->y < mapview.height) {
453     control_mouse_cursor(canvas_pos_to_tile(event->x, event->y));
454   } else {
455     update_mouse_cursor(CURSOR_DEFAULT);
456   }
457 
458   update_unit_info_label(get_units_in_focus());
459   return TRUE;
460 }
461 
462 /**************************************************************************
463   Overview canvas moved
464 **************************************************************************/
move_overviewcanvas(GtkWidget * w,GdkEventMotion * ev,gpointer data)465 gboolean move_overviewcanvas(GtkWidget *w, GdkEventMotion *ev, gpointer data)
466 {
467   overview_update_line(ev->x, ev->y);
468   return TRUE;
469 }
470 
471 /**************************************************************************
472   Button pressed at overview
473 **************************************************************************/
butt_down_overviewcanvas(GtkWidget * w,GdkEventButton * ev,gpointer data)474 gboolean butt_down_overviewcanvas(GtkWidget *w, GdkEventButton *ev, gpointer data)
475 {
476   int xtile, ytile;
477 
478   if (ev->type != GDK_BUTTON_PRESS)
479     return TRUE; /* Double-clicks? Triple-clicks? No thanks! */
480 
481   overview_to_map_pos(&xtile, &ytile, ev->x, ev->y);
482 
483   if (can_client_change_view() && (ev->button == 3)) {
484     center_tile_mapcanvas(map_pos_to_tile(xtile, ytile));
485   } else if (can_client_issue_orders() && (ev->button == 1)) {
486     do_map_click(map_pos_to_tile(xtile, ytile),
487 		 (ev->state & GDK_SHIFT_MASK) ? SELECT_APPEND : SELECT_POPUP);
488   }
489 
490   return TRUE;
491 }
492 
493 /**************************************************************************
494   Best effort to center the map on the currently selected unit(s)
495 **************************************************************************/
center_on_unit(void)496 void center_on_unit(void)
497 {
498   request_center_focus_unit();
499 }
500