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