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)
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 ***********************************************************************/
15 #include <fc_config.h>
31 #include "overview_common.h"
34 #include "client_main.h"
45 /* client/gui-gtk-4.0 */
59 struct tmousepos
{ int x
, y
; };
60 extern gint cur_x
, cur_y
;
62 /**************************************************************************
63 Button released when showing info label
64 **************************************************************************/
65 static gboolean
popit_button_release(GtkWidget
*w
, GdkEventButton
*ev
)
68 gdk_seat_ungrab(gdk_device_get_seat(ev
->device
));
69 gtk_widget_destroy(w
);
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 **************************************************************************/
80 static void popupinfo_positioning_callback(GtkWidget
*w
, GtkAllocation
*alloc
,
83 struct tmousepos
*mousepos
= data
;
87 ptile
= canvas_pos_to_tile(mousepos
->x
, mousepos
->y
);
88 if (tile_to_canvas_pos(&x
, &y
, ptile
)) {
89 gint minx
, miny
, maxy
;
91 gdk_window_get_origin(gtk_widget_get_window(map_canvas
), &minx
, &miny
);
92 maxy
= miny
+ gtk_widget_get_allocated_height(map_canvas
);
94 if (x
> mapview
.width
/2) {
95 /* right part of the map */
97 y
+= miny
+ (tileset_tile_height(tileset
) - alloc
->height
)/2;
99 y
= CLIP(miny
, y
, maxy
- alloc
->height
);
101 gtk_window_move(GTK_WINDOW(w
), x
- alloc
->width
, y
);
103 /* left part of the map */
104 x
+= minx
+ tileset_tile_width(tileset
);
105 y
+= miny
+ (tileset_tile_height(tileset
) - alloc
->height
)/2;
107 y
= CLIP(miny
, y
, maxy
- alloc
->height
);
109 gtk_window_move(GTK_WINDOW(w
), x
, y
);
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 **************************************************************************/
118 static void popit(GdkEventButton
*ev
, struct tile
*ptile
)
121 static struct tmousepos mousepos
;
124 if (TILE_UNKNOWN
!= client_tile_get_known(ptile
)) {
125 p
= gtk_window_new(GTK_WINDOW_POPUP
);
126 gtk_widget_set_margin_start(p
, 4);
127 gtk_widget_set_margin_end(p
, 4);
128 gtk_widget_set_margin_top(p
, 4);
129 gtk_widget_set_margin_bottom(p
, 4);
130 gtk_container_add(GTK_CONTAINER(p
), gtk_label_new(popup_info_text(ptile
)));
132 punit
= find_visible_unit(ptile
);
135 mapdeco_set_gotoroute(punit
);
136 if (punit
->goto_tile
) {
137 mapdeco_set_crosshair(punit
->goto_tile
, TRUE
);
140 mapdeco_set_crosshair(ptile
, TRUE
);
142 g_signal_connect(p
, "destroy",
143 G_CALLBACK(popupinfo_popdown_callback
), NULL
);
148 g_signal_connect(p
, "size-allocate",
149 G_CALLBACK(popupinfo_positioning_callback
),
153 gdk_seat_grab(gdk_device_get_seat(ev
->device
), gtk_widget_get_window(p
),
154 GDK_SEAT_CAPABILITY_ALL_POINTING
,
155 TRUE
, NULL
, (GdkEvent
*)ev
, NULL
, NULL
);
158 g_signal_connect_after(p
, "button_release_event",
159 G_CALLBACK(popit_button_release
), NULL
);
163 /**************************************************************************
164 Information label destruction requested
165 **************************************************************************/
166 void popupinfo_popdown_callback(GtkWidget
*w
, gpointer data
)
168 mapdeco_clear_crosshairs();
169 mapdeco_clear_gotoroutes();
172 /**************************************************************************
173 Callback from city name dialog for new city.
174 **************************************************************************/
175 static void name_new_city_popup_callback(gpointer data
, gint response
,
178 int idx
= GPOINTER_TO_INT(data
);
181 case GTK_RESPONSE_OK
:
182 finish_city(index_to_tile(&(wld
.map
), idx
), input
);
184 case GTK_RESPONSE_CANCEL
:
185 case GTK_RESPONSE_DELETE_EVENT
:
186 cancel_city(index_to_tile(&(wld
.map
), idx
));
191 /**************************************************************************
192 Popup dialog where the user choose the name of the new city
193 punit = (settler) unit which builds the city
194 suggestname = suggetion of the new city's name
195 **************************************************************************/
196 void popup_newcity_dialog(struct unit
*punit
, const char *suggestname
)
198 input_dialog_create(GTK_WINDOW(toplevel
), /*"shellnewcityname" */
200 _("What should we call our new city?"), suggestname
,
201 name_new_city_popup_callback
,
202 GINT_TO_POINTER(tile_index(unit_tile(punit
))));
205 /**************************************************************************
206 Enable or disable the turn done button.
207 Should probably some where else.
208 **************************************************************************/
209 void set_turn_done_button_state(bool state
)
211 gtk_widget_set_sensitive(turn_done_button
, state
);
214 /**************************************************************************
215 Handle 'Mouse button released'. Because of the quickselect feature,
216 the release of both left and right mousebutton can launch the goto.
217 **************************************************************************/
218 gboolean
butt_release_mapcanvas(GtkWidget
*w
, GdkEventButton
*ev
, gpointer data
)
220 if (editor_is_active()) {
221 return handle_edit_mouse_button_release(ev
);
224 if (ev
->button
== 1 || ev
->button
== 3) {
225 release_goto_button(ev
->x
, ev
->y
);
227 if (ev
->button
== 3 && (rbutton_down
|| hover_state
!= HOVER_NONE
)) {
228 release_right_button(ev
->x
, ev
->y
,
229 (ev
->state
& GDK_SHIFT_MASK
) != 0);
235 /**************************************************************************
236 Handle all mouse button press on canvas.
237 Future feature: User-configurable mouse clicks.
238 **************************************************************************/
239 gboolean
butt_down_mapcanvas(GtkWidget
*w
, GdkEventButton
*ev
, gpointer data
)
241 struct city
*pcity
= NULL
;
242 struct tile
*ptile
= NULL
;
244 if (editor_is_active()) {
245 return handle_edit_mouse_button_press(ev
);
248 if (!can_client_change_view()) {
252 gtk_widget_grab_focus(map_canvas
);
253 ptile
= canvas_pos_to_tile(ev
->x
, ev
->y
);
254 pcity
= ptile
? tile_city(ptile
) : NULL
;
256 switch (ev
->button
) {
258 case 1: /* LEFT mouse button */
260 /* <SHIFT> + <CONTROL> + LMB : Adjust workers. */
261 if ((ev
->state
& GDK_SHIFT_MASK
) && (ev
->state
& GDK_CONTROL_MASK
)) {
262 adjust_workers_button_pressed(ev
->x
, ev
->y
);
263 } else if (ev
->state
& GDK_CONTROL_MASK
) {
264 /* <CONTROL> + LMB : Quickselect a sea unit. */
265 action_button_pressed(ev
->x
, ev
->y
, SELECT_SEA
);
266 } else if (ptile
&& (ev
->state
& GDK_SHIFT_MASK
)) {
267 /* <SHIFT> + LMB: Append focus unit. */
268 action_button_pressed(ev
->x
, ev
->y
, SELECT_APPEND
);
269 } else if (ptile
&& (ev
->state
& GDK_MOD1_MASK
)) {
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. */
274 } else if (tiles_hilited_cities
) {
275 /* LMB in Area Selection mode. */
277 toggle_tile_hilite(ptile
);
280 /* Plain LMB click. */
281 action_button_pressed(ev
->x
, ev
->y
, SELECT_POPUP
);
285 case 2: /* MIDDLE mouse button */
287 /* <CONTROL> + MMB: Wake up sentries. */
288 if (ev
->state
& GDK_CONTROL_MASK
) {
289 wakeup_button_pressed(ev
->x
, ev
->y
);
291 /* Plain Middle click. */
296 case 3: /* RIGHT mouse button */
298 /* <CONTROL> + <ALT> + RMB : insert city or tile chat link. */
299 /* <CONTROL> + <ALT> + <SHIFT> + RMB : insert unit chat link. */
300 if (ptile
&& (ev
->state
& GDK_MOD1_MASK
)
301 && (ev
->state
& GDK_CONTROL_MASK
)) {
302 inputline_make_chat_link(ptile
, (ev
->state
& GDK_SHIFT_MASK
) != 0);
303 } else if ((ev
->state
& GDK_SHIFT_MASK
) && (ev
->state
& GDK_MOD1_MASK
)) {
304 /* <SHIFT> + <ALT> + RMB : Show/hide workers. */
305 key_city_overlay(ev
->x
, ev
->y
);
306 } else if ((ev
->state
& GDK_SHIFT_MASK
) && (ev
->state
& GDK_CONTROL_MASK
)
308 /* <SHIFT + CONTROL> + RMB: Paste Production. */
309 clipboard_paste_production(pcity
);
310 cancel_tile_hiliting();
311 } else if (ev
->state
& GDK_SHIFT_MASK
312 && clipboard_copy_production(ptile
)) {
313 /* <SHIFT> + RMB on city/unit: Copy Production. */
314 /* If nothing to copy, fall through to rectangle selection. */
316 /* Already done the copy */
317 } else if (ev
->state
& GDK_CONTROL_MASK
) {
318 /* <CONTROL> + RMB : Quickselect a land unit. */
319 action_button_pressed(ev
->x
, ev
->y
, SELECT_LAND
);
321 /* Plain RMB click. Area selection. */
322 /* A foolproof user will depress button on canvas,
323 * release it on another widget, and return to canvas
324 * to find rectangle still active.
326 if (rectangle_active
) {
327 release_right_button(ev
->x
, ev
->y
,
328 (ev
->state
& GDK_SHIFT_MASK
) != 0);
331 if (hover_state
== HOVER_NONE
) {
332 anchor_selection_rectangle(ev
->x
, ev
->y
);
333 rbutton_down
= TRUE
; /* causes rectangle updates */
345 /**************************************************************************
346 Update goto line so that destination is at current mouse pointer location.
347 **************************************************************************/
348 void create_line_at_mouse_pos(void)
351 GdkSeat
*seat
= gdk_display_get_default_seat(gtk_widget_get_display(toplevel
));
352 GdkDevice
*pointer
= gdk_seat_get_pointer(seat
);
359 window
= gdk_device_get_window_at_position(pointer
, &x
, &y
);
361 if (window
== gtk_widget_get_window(map_canvas
)) {
363 } else if (window
== gtk_widget_get_window(overview_canvas
)) {
364 overview_update_line(x
, y
);
369 /**************************************************************************
370 The Area Selection rectangle. Called by center_tile_mapcanvas() and
371 when the mouse pointer moves.
372 **************************************************************************/
373 void update_rect_at_mouse_pos(void)
378 GdkModifierType mask
;
379 GdkSeat
*seat
= gdk_display_get_default_seat(gtk_widget_get_display(toplevel
));
381 pointer
= gdk_seat_get_pointer(seat
);
386 window
= gdk_device_get_window_at_position(pointer
, &x
, &y
);
387 if (window
&& window
== gtk_widget_get_window(map_canvas
)) {
388 gdk_device_get_state(pointer
, window
, NULL
, &mask
);
389 if (mask
& GDK_BUTTON3_MASK
) {
390 update_selection_rectangle(x
, y
);
395 /**************************************************************************
396 Triggered by the mouse moving on the mapcanvas, this function will
397 update the mouse cursor and goto lines.
398 **************************************************************************/
399 gboolean
move_mapcanvas(GtkWidget
*w
, GdkEventMotion
*ev
, gpointer data
)
401 if (GUI_GTK_OPTION(mouse_over_map_focus
)
402 && !gtk_widget_has_focus(map_canvas
)) {
403 gtk_widget_grab_focus(map_canvas
);
406 if (editor_is_active()) {
407 return handle_edit_mouse_move(ev
);
412 update_line(ev
->x
, ev
->y
);
413 if (rbutton_down
&& (ev
->state
& GDK_BUTTON3_MASK
)) {
414 update_selection_rectangle(ev
->x
, ev
->y
);
417 if (keyboardless_goto_button_down
&& hover_state
== HOVER_NONE
) {
418 maybe_activate_keyboardless_goto(ev
->x
, ev
->y
);
420 control_mouse_cursor(canvas_pos_to_tile(ev
->x
, ev
->y
));
425 /**************************************************************************
426 This function will reset the mouse cursor if it leaves the map.
427 **************************************************************************/
428 gboolean
leave_mapcanvas(GtkWidget
*widget
, GdkEventCrossing
*event
)
430 if (gtk_notebook_get_current_page(GTK_NOTEBOOK(top_notebook
))
431 != gtk_notebook_page_num(GTK_NOTEBOOK(top_notebook
), map_widget
)) {
432 /* Map is not currently topmost tab. Do not use tile specific cursors. */
433 update_mouse_cursor(CURSOR_DEFAULT
);
437 /* Bizarrely, this function can be called even when we don't "leave"
438 * the map canvas, for instance, it gets called any time the mouse is
441 && event
->x
>= 0 && event
->y
>= 0
442 && event
->x
< mapview
.width
&& event
->y
< mapview
.height
) {
443 control_mouse_cursor(canvas_pos_to_tile(event
->x
, event
->y
));
445 update_mouse_cursor(CURSOR_DEFAULT
);
448 update_unit_info_label(get_units_in_focus());
452 /**************************************************************************
453 Overview canvas moved
454 **************************************************************************/
455 gboolean
move_overviewcanvas(GtkWidget
*w
, GdkEventMotion
*ev
, gpointer data
)
457 overview_update_line(ev
->x
, ev
->y
);
461 /**************************************************************************
462 Button pressed at overview
463 **************************************************************************/
464 gboolean
butt_down_overviewcanvas(GtkWidget
*w
, GdkEventButton
*ev
, gpointer data
)
468 if (ev
->type
!= GDK_BUTTON_PRESS
) {
469 return TRUE
; /* Double-clicks? Triple-clicks? No thanks! */
472 overview_to_map_pos(&xtile
, &ytile
, ev
->x
, ev
->y
);
474 if (can_client_change_view() && (ev
->button
== 3)) {
475 center_tile_mapcanvas(map_pos_to_tile(&(wld
.map
), xtile
, ytile
));
476 } else if (can_client_issue_orders() && (ev
->button
== 1)) {
477 do_map_click(map_pos_to_tile(&(wld
.map
), xtile
, ytile
),
478 (ev
->state
& GDK_SHIFT_MASK
) ? SELECT_APPEND
: SELECT_POPUP
);
484 /**************************************************************************
485 Best effort to center the map on the currently selected unit(s)
486 **************************************************************************/
487 void center_on_unit(void)
489 request_center_focus_unit();