1 /**********************************************************************
2 Freeciv - Copyright (C) 2002 - The Freeciv Poject
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>
18 #include <stdlib.h> /* qsort */
32 #include "chatline_common.h"
33 #include "cityrep_g.h"
34 #include "client_main.h"
41 #include "mapctrl_common.h"
42 #include "mapctrl_g.h"
43 #include "mapview_g.h"
45 #include "overview_common.h"
48 /* Selection Rectangle */
49 static int rec_anchor_x
, rec_anchor_y
; /* canvas coordinates for anchor */
50 static struct tile
*rec_canvas_center_tile
;
51 static int rec_corner_x
, rec_corner_y
; /* corner to iterate from */
52 static int rec_w
, rec_h
; /* width, heigth in pixels */
54 bool rbutton_down
= FALSE
;
55 bool rectangle_active
= FALSE
;
57 /* This changes the behaviour of left mouse
58 button in Area Selection mode. */
59 bool tiles_hilited_cities
= FALSE
;
61 /* The mapcanvas clipboard */
62 struct universal clipboard
=
64 .value
= {.building
= NULL
}
67 /* Goto with drag and drop. */
68 bool keyboardless_goto_button_down
= FALSE
;
69 bool keyboardless_goto_active
= FALSE
;
70 struct tile
*keyboardless_goto_start_tile
;
72 /* Update the workers for a city on the map, when the update is received */
73 struct city
*city_workers_display
= NULL
;
75 /*************************************************************************/
77 static void clipboard_send_production_packet(struct city
*pcity
);
78 static void define_tiles_within_rectangle(bool append
);
80 /**************************************************************************
81 Called when Right Mouse Button is depressed. Record the canvas
82 coordinates of the center of the tile, which may be unreal. This
83 anchor is not the drawing start point, but is used to calculate
84 width, height. Also record the current mapview centering.
85 **************************************************************************/
86 void anchor_selection_rectangle(int canvas_x
, int canvas_y
)
88 struct tile
*ptile
= canvas_pos_to_nearest_tile(canvas_x
, canvas_y
);
90 tile_to_canvas_pos(&rec_anchor_x
, &rec_anchor_y
, ptile
);
91 rec_anchor_x
+= tileset_tile_width(tileset
) / 2;
92 rec_anchor_y
+= tileset_tile_height(tileset
) / 2;
93 /* FIXME: This may be off-by-one. */
94 rec_canvas_center_tile
= get_center_tile_mapcanvas();
98 /**************************************************************************
99 Iterate over the pixel boundaries of the rectangle and pick the tiles
100 whose center falls within. Axis pixel incrementation is half tile size to
101 accomodate tilesets with varying tile shapes and proportions of X/Y.
103 These operations are performed on the tiles:
104 - Make tiles that contain owned cities hilited
105 on the map and hilited in the City List Window.
107 Later, I'll want to add unit hiliting for mass orders. -ali
109 NB: At the end of this function the current selection rectangle will be
110 erased (by being redrawn).
111 **************************************************************************/
112 static void define_tiles_within_rectangle(bool append
)
114 const int W
= tileset_tile_width(tileset
), half_W
= W
/ 2;
115 const int H
= tileset_tile_height(tileset
), half_H
= H
/ 2;
116 const int segments_x
= abs(rec_w
/ half_W
);
117 const int segments_y
= abs(rec_h
/ half_H
);
119 /* Iteration direction */
120 const int inc_x
= (rec_w
> 0 ? half_W
: -half_W
);
121 const int inc_y
= (rec_h
> 0 ? half_H
: -half_H
);
122 int x
, y
, x2
, y2
, xx
, yy
;
123 struct unit_list
*units
= unit_list_new();
125 bool found_any_cities
= FALSE
;
128 cancel_tile_hiliting();
132 for (yy
= 0; yy
<= segments_y
; yy
++, y
+= inc_y
) {
134 for (xx
= 0; xx
<= segments_x
; xx
++, x
+= inc_x
) {
137 /* For diamond shaped tiles, every other row is indented.
139 if ((yy
% 2 ^ xx
% 2) != 0) {
143 ptile
= canvas_pos_to_tile(x
, y
);
148 /* "Half-tile" indentation must match, or we'll process
149 * some tiles twice in the case of rectangular shape tiles.
151 tile_to_canvas_pos(&x2
, &y2
, ptile
);
153 if ((yy
% 2) != 0 && ((rec_corner_x
% W
) ^ abs(x2
% W
)) != 0) {
157 /* Tile passed all tests; process it.
159 if (NULL
!= tile_city(ptile
)
160 && tile_owner(ptile
) == client
.conn
.playing
) {
161 mapdeco_set_highlight(ptile
, TRUE
);
162 found_any_cities
= tiles_hilited_cities
= TRUE
;
164 unit_list_iterate(ptile
->units
, punit
) {
165 if (unit_owner(punit
) == client
.conn
.playing
) {
166 unit_list_append(units
, punit
);
168 } unit_list_iterate_end
;
172 if (!(separate_unit_selection
&& found_any_cities
)
173 && unit_list_size(units
) > 0) {
175 struct unit
*punit
= unit_list_get(units
, 0);
176 unit_focus_set(punit
);
177 unit_list_remove(units
, punit
);
179 unit_list_iterate(units
, punit
) {
180 unit_focus_add(punit
);
181 } unit_list_iterate_end
;
183 unit_list_destroy(units
);
185 /* Clear previous rectangle. */
186 draw_selection_rectangle(rec_corner_x
, rec_corner_y
, rec_w
, rec_h
);
188 /* Hilite in City List Window */
189 if (tiles_hilited_cities
) {
190 hilite_cities_from_canvas(); /* cityrep.c */
194 /**************************************************************************
195 Called when mouse pointer moves and rectangle is active.
196 **************************************************************************/
197 void update_selection_rectangle(int canvas_x
, int canvas_y
)
199 const int W
= tileset_tile_width(tileset
), half_W
= W
/ 2;
200 const int H
= tileset_tile_height(tileset
), half_H
= H
/ 2;
201 static struct tile
*rec_tile
= NULL
;
203 struct tile
*center_tile
;
206 ptile
= canvas_pos_to_nearest_tile(canvas_x
, canvas_y
);
208 /* Did mouse pointer move beyond the current tile's
209 * boundaries? Avoid macros; tile may be unreal!
211 if (ptile
== rec_tile
) {
216 /* Clear previous rectangle. */
217 draw_selection_rectangle(rec_corner_x
, rec_corner_y
, rec_w
, rec_h
);
219 /* Fix canvas coords to the center of the tile.
221 tile_to_canvas_pos(&canvas_x
, &canvas_y
, ptile
);
225 rec_w
= rec_anchor_x
- canvas_x
; /* width */
226 rec_h
= rec_anchor_y
- canvas_y
; /* height */
228 /* FIXME: This may be off-by-one. */
229 center_tile
= get_center_tile_mapcanvas();
230 map_distance_vector(&diff_x
, &diff_y
, center_tile
, rec_canvas_center_tile
);
232 /* Adjust width, height if mapview has recentered.
234 if (diff_x
!= 0 || diff_y
!= 0) {
236 if (tileset_is_isometric(tileset
)) {
237 rec_w
+= (diff_x
- diff_y
) * half_W
;
238 rec_h
+= (diff_x
+ diff_y
) * half_H
;
241 if (abs(rec_w
) > map
.xsize
* half_W
/ 2) {
242 int wx
= map
.xsize
* half_W
, wy
= map
.xsize
* half_H
;
243 rec_w
> 0 ? (rec_w
-= wx
, rec_h
-= wy
) : (rec_w
+= wx
, rec_h
+= wy
);
251 if (abs(rec_w
) > map
.xsize
* half_W
) {
252 int wx
= map
.xsize
* W
;
253 rec_w
> 0 ? (rec_w
-= wx
) : (rec_w
+= wx
);
258 if (rec_w
== 0 && rec_h
== 0) {
259 rectangle_active
= FALSE
;
263 /* It is currently drawn only to the screen, not backing store */
264 rectangle_active
= TRUE
;
265 draw_selection_rectangle(canvas_x
, canvas_y
, rec_w
, rec_h
);
266 rec_corner_x
= canvas_x
;
267 rec_corner_y
= canvas_y
;
270 /**************************************************************************
271 Redraws the selection rectangle after a map flush.
272 **************************************************************************/
273 void redraw_selection_rectangle(void)
275 if (rectangle_active
) {
276 draw_selection_rectangle(rec_corner_x
, rec_corner_y
, rec_w
, rec_h
);
280 /**************************************************************************
281 Redraws the selection rectangle after a map flush.
282 **************************************************************************/
283 void cancel_selection_rectangle(void)
285 if (rectangle_active
) {
286 rectangle_active
= FALSE
;
287 rbutton_down
= FALSE
;
289 /* Erase the previously drawn selection rectangle. */
290 draw_selection_rectangle(rec_corner_x
, rec_corner_y
, rec_w
, rec_h
);
294 /**************************************************************************
296 **************************************************************************/
297 bool is_city_hilited(struct city
*pcity
)
299 return pcity
&& mapdeco_is_highlight_set(city_tile(pcity
));
302 /**************************************************************************
303 Remove hiliting from all tiles, but not from rows in the City List window.
304 **************************************************************************/
305 void cancel_tile_hiliting(void)
307 if (tiles_hilited_cities
) {
308 tiles_hilited_cities
= FALSE
;
309 mapdeco_clear_highlights();
313 /**************************************************************************
314 Action depends on whether the mouse pointer moved
315 a tile between press and release.
316 **************************************************************************/
317 void release_right_button(int canvas_x
, int canvas_y
, bool shift
)
319 if (rectangle_active
) {
320 define_tiles_within_rectangle(shift
);
322 cancel_tile_hiliting();
323 recenter_button_pressed(canvas_x
, canvas_y
);
325 rectangle_active
= FALSE
;
326 rbutton_down
= FALSE
;
329 /**************************************************************************
330 Left Mouse Button in Area Selection mode.
331 **************************************************************************/
332 void toggle_tile_hilite(struct tile
*ptile
)
334 struct city
*pcity
= tile_city(ptile
);
336 if (mapdeco_is_highlight_set(ptile
)) {
337 mapdeco_set_highlight(ptile
, FALSE
);
339 toggle_city_hilite(pcity
, FALSE
); /* cityrep.c */
342 else if (NULL
!= pcity
&& city_owner(pcity
) == client
.conn
.playing
) {
343 mapdeco_set_highlight(ptile
, TRUE
);
344 tiles_hilited_cities
= TRUE
;
345 toggle_city_hilite(pcity
, TRUE
);
352 /**************************************************************************
353 The user pressed the overlay-city button (t) while the mouse was at the
354 given canvas position.
355 **************************************************************************/
356 void key_city_overlay(int canvas_x
, int canvas_y
)
358 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
360 if (can_client_change_view() && ptile
) {
362 struct city
*pcity
= find_city_or_settler_near_tile(ptile
, &punit
);
365 toggle_city_color(pcity
);
367 toggle_unit_color(punit
);
372 /**************************************************************************
373 Shift-Left-Click on owned city or any visible unit to copy.
374 Returns whether it found anything to try to copy.
375 **************************************************************************/
376 bool clipboard_copy_production(struct tile
*ptile
)
379 struct city
*pcity
= tile_city(ptile
);
381 if (!can_client_issue_orders()) {
386 if (city_owner(pcity
) != client
.conn
.playing
) {
389 clipboard
= pcity
->production
;
391 struct unit
*punit
= find_visible_unit(ptile
);
395 if (!can_player_build_unit_direct(client
.conn
.playing
, unit_type(punit
))) {
396 create_event(ptile
, E_BAD_COMMAND
, ftc_client
,
397 _("You don't know how to build %s!"),
398 unit_name_translation(punit
));
401 clipboard
.kind
= VUT_UTYPE
;
402 clipboard
.value
.utype
= unit_type(punit
);
404 upgrade_canvas_clipboard();
406 create_event(ptile
, E_CITY_PRODUCTION_CHANGED
, /* ? */
407 ftc_client
, _("Copy %s to clipboard."),
408 universal_name_translation(&clipboard
, buffer
, sizeof(buffer
)));
412 /**************************************************************************
413 If City tiles are hilited, paste into all those cities.
414 Otherwise paste into the one city under the mouse pointer.
415 **************************************************************************/
416 void clipboard_paste_production(struct city
*pcity
)
418 if (!can_client_issue_orders()) {
421 if (NULL
== clipboard
.value
.building
) {
422 create_event(city_tile(pcity
), E_BAD_COMMAND
, ftc_client
,
423 _("Clipboard is empty."));
426 if (!tiles_hilited_cities
) {
427 if (NULL
!= pcity
&& city_owner(pcity
) == client
.conn
.playing
) {
428 clipboard_send_production_packet(pcity
);
433 connection_do_buffer(&client
.conn
);
434 city_list_iterate(client
.conn
.playing
->cities
, pcity
) {
435 if (is_city_hilited(pcity
)) {
436 clipboard_send_production_packet(pcity
);
438 } city_list_iterate_end
;
439 connection_do_unbuffer(&client
.conn
);
443 /**************************************************************************
444 Send request to build production in clipboard to server.
445 **************************************************************************/
446 static void clipboard_send_production_packet(struct city
*pcity
)
448 if (are_universals_equal(&pcity
->production
, &clipboard
)
449 || !can_city_build_now(pcity
, clipboard
)) {
453 dsend_packet_city_change(&client
.conn
, pcity
->id
,
455 universal_number(&clipboard
));
458 /**************************************************************************
459 A newer technology may be available for units.
460 Also called from packhand.c.
461 **************************************************************************/
462 void upgrade_canvas_clipboard(void)
464 if (!can_client_issue_orders()) {
467 if (VUT_UTYPE
== clipboard
.kind
) {
468 struct unit_type
*u
=
469 can_upgrade_unittype(client
.conn
.playing
, clipboard
.value
.utype
);
472 clipboard
.value
.utype
= u
;
477 /**************************************************************************
478 Goto button has been released. Finish goto.
479 **************************************************************************/
480 void release_goto_button(int canvas_x
, int canvas_y
)
482 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
484 if (keyboardless_goto_active
&& hover_state
== HOVER_GOTO
&& ptile
) {
486 set_hover_state(NULL
, HOVER_NONE
, ACTIVITY_LAST
, NULL
, ORDER_LAST
);
487 update_unit_info_label(get_units_in_focus());
489 keyboardless_goto_active
= FALSE
;
490 keyboardless_goto_button_down
= FALSE
;
491 keyboardless_goto_start_tile
= NULL
;
494 /**************************************************************************
495 The goto hover state is only activated when the mouse pointer moves
496 beyond the tile where the button was depressed, to avoid mouse typos.
497 **************************************************************************/
498 void maybe_activate_keyboardless_goto(int canvas_x
, int canvas_y
)
500 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
502 if (ptile
&& get_num_units_in_focus() > 0
503 && !same_pos(keyboardless_goto_start_tile
, ptile
)
504 && can_client_issue_orders()) {
505 keyboardless_goto_active
= TRUE
;
506 request_unit_goto(ORDER_LAST
);
510 /**************************************************************************
511 Return TRUE iff the turn done button should be enabled.
512 **************************************************************************/
513 bool get_turn_done_button_state(void)
515 return (can_client_issue_orders()
516 && !client
.conn
.playing
->ai_controlled
517 && !client
.conn
.playing
->phase_done
521 /**************************************************************************
522 Scroll the mapview half a screen in the given direction. This is a GUI
523 direction; i.e., DIR8_NORTH is "up" on the mapview.
524 **************************************************************************/
525 void scroll_mapview(enum direction8 gui_dir
)
527 int gui_x
= mapview
.gui_x0
, gui_y
= mapview
.gui_y0
;
529 if (!can_client_change_view()) {
533 gui_x
+= DIR_DX
[gui_dir
] * mapview
.width
/ 2;
534 gui_y
+= DIR_DY
[gui_dir
] * mapview
.height
/ 2;
535 set_mapview_origin(gui_x
, gui_y
);
538 /**************************************************************************
539 Do some appropriate action when the "main" mouse button (usually
540 left-click) is pressed. For more sophisticated user control use (or
541 write) a different xxx_button_pressed function.
542 **************************************************************************/
543 void action_button_pressed(int canvas_x
, int canvas_y
,
544 enum quickselect_type qtype
)
546 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
548 if (can_client_change_view() && ptile
) {
549 /* FIXME: Some actions here will need to check can_client_issue_orders.
550 * But all we can check is the lowest common requirement. */
551 do_map_click(ptile
, qtype
);
555 /**************************************************************************
556 Wakeup sentried units on the tile of the specified location.
557 **************************************************************************/
558 void wakeup_button_pressed(int canvas_x
, int canvas_y
)
560 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
562 if (can_client_issue_orders() && ptile
) {
563 wakeup_sentried_units(ptile
);
567 /**************************************************************************
568 Adjust the position of city workers from the mapview.
569 **************************************************************************/
570 void adjust_workers_button_pressed(int canvas_x
, int canvas_y
)
572 struct tile
*ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
574 if (NULL
!= ptile
&& can_client_issue_orders()) {
575 struct city
*pcity
= find_city_near_tile(ptile
);
577 if (pcity
&& !cma_is_city_under_agent(pcity
, NULL
)) {
580 fc_assert_ret(city_base_to_city_map(&city_x
, &city_y
, pcity
, ptile
));
582 if (NULL
!= tile_worked(ptile
) && tile_worked(ptile
) == pcity
) {
583 dsend_packet_city_make_specialist(&client
.conn
, pcity
->id
,
585 } else if (city_can_work_tile(pcity
, ptile
)) {
586 dsend_packet_city_make_worker(&client
.conn
, pcity
->id
,
592 /* When the city info packet is received, update the workers on the
593 * map. This is a bad hack used to selectively update the mapview
594 * when we receive the corresponding city packet. */
595 city_workers_display
= pcity
;
600 /**************************************************************************
601 Recenter the map on the canvas location, on user request. Usually this
602 is done with a right-click.
603 **************************************************************************/
604 void recenter_button_pressed(int canvas_x
, int canvas_y
)
606 /* We use the "nearest" tile here so off-map clicks will still work. */
607 struct tile
*ptile
= canvas_pos_to_nearest_tile(canvas_x
, canvas_y
);
609 if (can_client_change_view() && ptile
) {
610 center_tile_mapcanvas(ptile
);
614 /**************************************************************************
615 Update the turn done button state.
616 **************************************************************************/
617 void update_turn_done_button_state(void)
619 bool turn_done_state
= get_turn_done_button_state();
621 set_turn_done_button_state(turn_done_state
);
623 if (turn_done_state
) {
624 if (waiting_for_end_turn
625 || (NULL
!= client
.conn
.playing
626 && client
.conn
.playing
->ai_controlled
627 && !ai_manual_turn_done
)) {
630 update_turn_done_button(TRUE
);
635 /**************************************************************************
636 Update the goto/patrol line to the given map canvas location.
637 **************************************************************************/
638 void update_line(int canvas_x
, int canvas_y
)
642 switch (hover_state
) {
646 ptile
= canvas_pos_to_tile(canvas_x
, canvas_y
);
648 is_valid_goto_draw_line(ptile
);
654 /****************************************************************************
655 Update the goto/patrol line to the given overview canvas location.
656 ****************************************************************************/
657 void overview_update_line(int overview_x
, int overview_y
)
662 switch (hover_state
) {
666 overview_to_map_pos(&x
, &y
, overview_x
, overview_y
);
667 ptile
= map_pos_to_tile(x
, y
);
669 is_valid_goto_draw_line(ptile
);
675 /****************************************************************************
676 We sort according to the following logic:
678 - Transported units should immediately follow their transporter (note that
679 transporting may be recursive).
680 - Otherwise we sort by ID (which is what the list is originally sorted by).
681 ****************************************************************************/
682 static int unit_list_compare(const void *a
, const void *b
)
684 const struct unit
*punit1
= *(struct unit
**)a
;
685 const struct unit
*punit2
= *(struct unit
**)b
;
687 if (unit_transport_get(punit1
) == unit_transport_get(punit2
)) {
688 /* For units with the same transporter or no transporter: sort by id. */
689 /* Perhaps we should sort by name instead? */
690 return punit1
->id
- punit2
->id
;
691 } else if (unit_transport_get(punit1
) == punit2
) {
693 } else if (unit_transport_get(punit2
) == punit1
) {
696 /* If the transporters aren't the same, put in order by the
698 const struct unit
*ptrans1
= unit_transport_get(punit1
);
699 const struct unit
*ptrans2
= unit_transport_get(punit2
);
708 return unit_list_compare(&ptrans1
, &ptrans2
);
712 /****************************************************************************
713 Fill and sort the list of units on the tile.
714 ****************************************************************************/
715 void fill_tile_unit_list(const struct tile
*ptile
, struct unit
**unit_list
)
719 /* First populate the unit list. */
720 unit_list_iterate(ptile
->units
, punit
) {
721 unit_list
[i
] = punit
;
723 } unit_list_iterate_end
;
726 qsort(unit_list
, i
, sizeof(*unit_list
), unit_list_compare
);