Freeciv-3.3: /fast/freeciv/nightly/main/client/gui-gtk-4.0/chatline.c Source File

Freeciv-3.3

Loading...
Searching...
No Matches
chatline.c
Go to the documentation of this file.
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#include <time.h>
22
23#include <gdk/gdkkeysyms.h>
24
25/* utility */
26#include "fcintl.h"
27#include "genlist.h"
28#include "log.h"
29#include "mem.h"
30#include "support.h"
31
32/* common */
33#include "chat.h"
34#include "featured_text.h"
35#include "game.h"
36#include "packets.h"
37
38/* client */
39#include "client_main.h"
40#include "climap.h"
41#include "control.h"
42#include "mapview_common.h"
43#include "update_queue.h"
44
45/* client/gui-gtk-4.0 */
46#include "colors.h"
47#include "gui_main.h"
48#include "gui_stuff.h"
49#include "menu.h"
50#include "pages.h"
51
52#include "chatline.h"
53
54#define MAX_CHATLINE_HISTORY 20
55
56 static struct genlist *history_list = NULL;
57 static int history_pos = -1;
58
59static struct inputline_toolkit {
60 GtkWidget *main_widget;
61 GtkWidget *entry;
62 GtkWidget *button_box;
63 GtkWidget *toolbar;
64 GtkWidget *toggle_button;
65 bool toolbar_displayed;
66} toolkit; /* Singleton. */
67
68static void inputline_make_tag(GtkEntry *entry, enum text_tag_type type);
69
70/**********************************************************************/
73 bool inputline_has_focus(void)
74{
75 return gtk_widget_has_focus(toolkit.entry);
76}
77
78/**********************************************************************/
81 void inputline_grab_focus(void)
82{
83 gtk_widget_grab_focus(toolkit.entry);
84}
85
86/**********************************************************************/
89 bool inputline_is_visible(void)
90{
91 return gtk_widget_get_mapped(toolkit.entry);
92}
93
94/**********************************************************************/
97 static gboolean il_lost_focus(GtkEventControllerFocus *controller,
98 gpointer data)
99{
100 real_menus_update();
101
102 return TRUE;
103}
104
105/**********************************************************************/
108 static gboolean il_gained_focus(GtkEventControllerFocus *controller,
109 gpointer data)
110{
111 menus_disable_unit_commands();
112
113 return TRUE;
114}
115
116/**********************************************************************/
121 static bool is_plain_public_message(const char *s)
122{
123 const char *p;
124
125 /* If it is a server command or an explicit ally
126 * message, then it is not a public message. */
127 if (s[0] == SERVER_COMMAND_PREFIX || s[0] == CHAT_ALLIES_PREFIX) {
128 return FALSE;
129 }
130
131 /* It might be a private message of the form
132 * 'player name with spaces':the message
133 * or with ". So skip past the player name part. */
134 if (s[0] == '\'' || s[0] == '"') {
135 p = strchr(s + 1, s[0]);
136 } else {
137 p = s;
138 }
139
140 /* Now we just need to check that it is not a private
141 * message. If we encounter a space then the preceeding
142 * text could not have been a user/player name (the
143 * quote check above eliminated names with spaces) so
144 * it must be a public message. Otherwise if we encounter
145 * the message prefix : then the text parsed up until now
146 * was a player/user name and the line is intended as
147 * a private message (or explicit public message if the
148 * first character is :). */
149 while (p != NULL && *p != '\0') {
150 if (fc_isspace(*p)) {
151 return TRUE;
152 } else if (*p == CHAT_DIRECT_PREFIX) {
153 return FALSE;
154 }
155 p++;
156 }
157 return TRUE;
158}
159
160
161/**********************************************************************/
164 static void inputline_return(GtkEntry *w, gpointer data)
165{
166 const char *theinput;
167 GtkEntryBuffer *buffer = gtk_entry_get_buffer(w);
168
169 theinput = gtk_entry_buffer_get_text(buffer);
170
171 if (*theinput) {
172 if (client_state() == C_S_RUNNING
173 && GUI_GTK_OPTION(allied_chat_only)
174 && is_plain_public_message(theinput)) {
175 char buf[MAX_LEN_MSG];
176
177 fc_snprintf(buf, sizeof(buf), ". %s", theinput);
178 send_chat(buf);
179 } else {
180 send_chat(theinput);
181 }
182
183 if (genlist_size(history_list) >= MAX_CHATLINE_HISTORY) {
184 void *history_data;
185
186 history_data = genlist_get(history_list, -1);
187 genlist_remove(history_list, history_data);
188 free(history_data);
189 }
190
191 genlist_prepend(history_list, fc_strdup(theinput));
192 history_pos = -1;
193 }
194
195 gtk_entry_buffer_set_text(buffer, "", -1);
196}
197
198/**********************************************************************/
201 static const char *get_player_or_user_name(int id)
202{
203 size_t size = conn_list_size(game.all_connections);
204
205 if (id < size) {
206 return conn_list_get(game.all_connections, id)->username;
207 } else {
208 struct player *pplayer = player_by_number(id - size);
209 if (pplayer) {
210 return pplayer->name;
211 } else {
212 /* Empty slot. Relies on being used with comparison function
213 * which can cope with NULL. */
214 return NULL;
215 }
216 }
217}
218
219/**********************************************************************/
228 static int check_player_or_user_name(const char *prefix,
229 const char **matches,
230 const int max_matches)
231{
232 int matches_id[max_matches * 2], ind, num;
233
234 switch (match_prefix_full(get_player_or_user_name,
235 player_slot_count()
236 + conn_list_size(game.all_connections),
237 MAX_LEN_NAME, fc_strncasecmp, strlen,
238 prefix, &ind, matches_id,
239 max_matches * 2, &num)) {
240 case M_PRE_EXACT:
241 case M_PRE_ONLY:
242 matches[0] = get_player_or_user_name(ind);
243 return 1;
244 case M_PRE_AMBIGUOUS:
245 {
246 /* Remove duplications playername/username. */
247 const char *name;
248 int i, j, c = 0;
249
250 for (i = 0; i < num && c < max_matches; i++) {
251 name = get_player_or_user_name(matches_id[i]);
252 for (j = 0; j < c; j++) {
253 if (0 == fc_strncasecmp(name, matches[j], MAX_LEN_NAME)) {
254 break;
255 }
256 }
257 if (j >= c) {
258 matches[c++] = name;
259 }
260 }
261 return c;
262 }
263 case M_PRE_EMPTY:
264 case M_PRE_LONG:
265 case M_PRE_FAIL:
266 case M_PRE_LAST:
267 break;
268 }
269
270 return 0;
271}
272
273/**********************************************************************/
283 static size_t get_common_prefix(const char *const *prefixes,
284 size_t num_prefixes,
285 char *buf, size_t buf_len)
286{
287 const char *p;
288 char *q;
289 size_t i;
290
291 fc_strlcpy(buf, prefixes[0], buf_len);
292 for (i = 1; i < num_prefixes; i++) {
293 for (p = prefixes[i], q = buf; *p != '\0' && *q != '\0';
294 p = g_utf8_next_char(p), q = g_utf8_next_char(q)) {
295 if (g_unichar_toupper(g_utf8_get_char(p))
296 != g_unichar_toupper(g_utf8_get_char(q))) {
297 *q = '\0';
298 break;
299 }
300 }
301 }
302
303 return g_utf8_strlen(buf, -1);
304}
305
306/**********************************************************************/
310 static bool chatline_autocomplete(GtkEditable *editable)
311{
312#define MAX_MATCHES 10
313 const char *name[MAX_MATCHES];
314 char buf[MAX_LEN_NAME * MAX_MATCHES];
315 gint pos;
316 gchar *chars, *p, *prev;
317 int num, i;
318 size_t prefix_len;
319
320 /* Part 1: get the string to complete. */
321 pos = gtk_editable_get_position(editable);
322 chars = gtk_editable_get_chars(editable, 0, pos);
323
324 p = chars + strlen(chars);
325 while ((prev = g_utf8_find_prev_char(chars, p))) {
326 if (!g_unichar_isalnum(g_utf8_get_char(prev))) {
327 break;
328 }
329 p = prev;
330 }
331 /* p points to the start of the last word, or the start of the string. */
332
333 prefix_len = g_utf8_strlen(p, -1);
334 if (0 == prefix_len) {
335 /* Empty: nothing to complete, propagate the event. */
336 g_free(chars);
337 return FALSE;
338 }
339
340 /* Part 2: Compare with player and user names. */
341 num = check_player_or_user_name(p, name, MAX_MATCHES);
342 if (1 == num) {
343 gtk_editable_delete_text(editable, pos - prefix_len, pos);
344 pos -= prefix_len;
345 gtk_editable_insert_text(editable, name[0], strlen(name[0]), &pos);
346 gtk_editable_set_position(editable, pos);
347 g_free(chars);
348 return TRUE;
349 } else if (num > 1) {
350 if (get_common_prefix(name, num, buf, sizeof(buf)) > prefix_len) {
351 gtk_editable_delete_text(editable, pos - prefix_len, pos);
352 pos -= prefix_len;
353 gtk_editable_insert_text(editable, buf, strlen(buf), &pos);
354 gtk_editable_set_position(editable, pos);
355 }
356 sz_strlcpy(buf, name[0]);
357 for (i = 1; i < num; i++) {
358 cat_snprintf(buf, sizeof(buf), ", %s", name[i]);
359 }
360 /* TRANS: comma-separated list of player/user names for completion */
361 output_window_printf(ftc_client, _("Suggestions: %s."), buf);
362 }
363
364 g_free(chars);
365
366 return TRUE;
367}
368
369/**********************************************************************/
372 static gboolean inputline_handler(GtkEventControllerKey *controller,
373 guint keyval,
374 guint keycode,
375 GdkModifierType state,
376 gpointer data)
377{
378 GtkWidget *w = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(controller));
379
380 if ((state & GDK_CONTROL_MASK)) {
381 /* Chatline featured text support. */
382
383 switch (keyval) {
384 case GDK_KEY_b:
385 inputline_make_tag(GTK_ENTRY(w), TTT_BOLD);
386 return TRUE;
387
388 case GDK_KEY_c:
389 inputline_make_tag(GTK_ENTRY(w), TTT_COLOR);
390 return TRUE;
391
392 case GDK_KEY_i:
393 inputline_make_tag(GTK_ENTRY(w), TTT_ITALIC);
394 return TRUE;
395
396 case GDK_KEY_s:
397 inputline_make_tag(GTK_ENTRY(w), TTT_STRIKE);
398 return TRUE;
399
400 case GDK_KEY_u:
401 inputline_make_tag(GTK_ENTRY(w), TTT_UNDERLINE);
402 return TRUE;
403
404 default:
405 break;
406 }
407
408 } else {
409 /* Chatline history controls. */
410 GtkEntryBuffer *buffer = gtk_entry_get_buffer(GTK_ENTRY(w));
411
412 switch (keyval) {
413 case GDK_KEY_Up:
414 if (history_pos < genlist_size(history_list) - 1) {
415 gtk_entry_buffer_set_text(buffer,
416 genlist_get(history_list, ++history_pos),
417 -1);
418 gtk_editable_set_position(GTK_EDITABLE(w), -1);
419 }
420 return TRUE;
421
422 case GDK_KEY_Down:
423 if (history_pos >= 0) {
424 history_pos--;
425 }
426
427 if (history_pos >= 0) {
428 gtk_entry_buffer_set_text(buffer,
429 genlist_get(history_list, history_pos),
430 -1);
431 } else {
432 gtk_entry_buffer_set_text(buffer, "", -1);
433 }
434 gtk_editable_set_position(GTK_EDITABLE(w), -1);
435 return TRUE;
436
437 case GDK_KEY_Tab:
438 if (GUI_GTK_OPTION(chatline_autocompletion)) {
439 return chatline_autocomplete(GTK_EDITABLE(w));
440 }
441 return FALSE;
442
443 default:
444 break;
445 }
446 }
447
448 return FALSE;
449}
450
451/**********************************************************************/
454 void inputline_make_tag(GtkEntry *entry, enum text_tag_type type)
455{
456 char buf[MAX_LEN_MSG];
457 GtkEditable *editable = GTK_EDITABLE(entry);
458 gint start_pos, end_pos;
459 gchar *selection;
460 gchar *fg_color_text = NULL, *bg_color_text = NULL;
461
462 if (!gtk_editable_get_selection_bounds(editable, &start_pos, &end_pos)) {
463 /* Let's say the selection starts and ends at the current position. */
464 start_pos = end_pos = gtk_editable_get_position(editable);
465 }
466
467 selection = gtk_editable_get_chars(editable, start_pos, end_pos);
468
469 if (type == TTT_COLOR) {
470 /* Get the color arguments. */
471 GdkRGBA *fg_color = g_object_get_data(G_OBJECT(entry), "fg_color");
472 GdkRGBA *bg_color = g_object_get_data(G_OBJECT(entry), "bg_color");
473
474 if (!fg_color && !bg_color) {
475 goto CLEAN_UP;
476 }
477
478 if (fg_color) {
479 fg_color_text = gdk_rgba_to_string(fg_color);
480 }
481 if (bg_color) {
482 bg_color_text = gdk_rgba_to_string(bg_color);
483 }
484
485 if (0 == featured_text_apply_tag(selection, buf, sizeof(buf),
486 TTT_COLOR, 0, FT_OFFSET_UNSET,
487 ft_color_construct(fg_color_text,
488 bg_color_text))) {
489 goto CLEAN_UP;
490 }
491 } else if (0 == featured_text_apply_tag(selection, buf, sizeof(buf),
492 type, 0, FT_OFFSET_UNSET)) {
493 goto CLEAN_UP;
494 }
495
496 /* Replace the selection. */
497 gtk_editable_delete_text(editable, start_pos, end_pos);
498 end_pos = start_pos;
499 gtk_editable_insert_text(editable, buf, -1, &end_pos);
500 gtk_editable_select_region(editable, start_pos, end_pos);
501
502 CLEAN_UP:
503 g_free(selection);
504 g_free(fg_color_text);
505 g_free(bg_color_text);
506}
507
508/**********************************************************************/
512 void inputline_make_chat_link(struct tile *ptile, bool unit)
513{
514 char buf[MAX_LEN_MSG];
515 GtkWidget *entry = toolkit.entry;
516 GtkEditable *editable = GTK_EDITABLE(entry);
517 gint start_pos, end_pos;
518 gchar *chars;
519 struct unit *punit;
520
521 /* Get the target. */
522 if (unit) {
523 punit = find_visible_unit(ptile);
524 if (!punit) {
525 output_window_append(ftc_client, _("No visible unit on this tile."));
526 return;
527 }
528 } else {
529 punit = NULL;
530 }
531
532 if (gtk_editable_get_selection_bounds(editable, &start_pos, &end_pos)) {
533 /* There is a selection, make it clickable. */
534 gpointer target;
535 enum text_link_type type;
536
537 chars = gtk_editable_get_chars(editable, start_pos, end_pos);
538 if (punit) {
539 type = TLT_UNIT;
540 target = punit;
541 } else if (tile_city(ptile)) {
542 type = TLT_CITY;
543 target = tile_city(ptile);
544 } else {
545 type = TLT_TILE;
546 target = ptile;
547 }
548
549 if (0 != featured_text_apply_tag(chars, buf, sizeof(buf), TTT_LINK,
550 0, FT_OFFSET_UNSET, type, target)) {
551 /* Replace the selection. */
552 gtk_editable_delete_text(editable, start_pos, end_pos);
553 end_pos = start_pos;
554 gtk_editable_insert_text(editable, buf, -1, &end_pos);
555 gtk_widget_grab_focus(entry);
556 gtk_editable_select_region(editable, start_pos, end_pos);
557 }
558 } else {
559 /* Just insert the link at the current position. */
560 start_pos = gtk_editable_get_position(editable);
561 end_pos = start_pos;
562 chars = gtk_editable_get_chars(editable, MAX(start_pos - 1, 0),
563 start_pos + 1);
564 if (punit) {
565 sz_strlcpy(buf, unit_link(punit));
566 } else if (tile_city(ptile)) {
567 sz_strlcpy(buf, city_link(tile_city(ptile)));
568 } else {
569 sz_strlcpy(buf, tile_link(ptile));
570 }
571
572 if (start_pos > 0 && strlen(chars) > 0 && chars[0] != ' ') {
573 /* Maybe insert an extra space. */
574 gtk_editable_insert_text(editable, " ", 1, &end_pos);
575 }
576 gtk_editable_insert_text(editable, buf, -1, &end_pos);
577 if (chars[start_pos > 0 ? 1 : 0] != '\0'
578 && chars[start_pos > 0 ? 1 : 0] != ' ') {
579 /* Maybe insert an extra space. */
580 gtk_editable_insert_text(editable, " ", 1, &end_pos);
581 }
582 gtk_widget_grab_focus(entry);
583 gtk_editable_set_position(editable, end_pos);
584 }
585
586 g_free(chars);
587}
588
589/**********************************************************************/
595 void scroll_if_necessary(GtkTextView *textview, GtkTextMark *scroll_target)
596{
597 GtkWidget *sw;
598 GtkAdjustment *vadj;
599 gdouble val, max, upper, page_size;
600
601 fc_assert_ret(textview != NULL);
602 fc_assert_ret(scroll_target != NULL);
603
604 sw = gtk_widget_get_parent(GTK_WIDGET(textview));
605 fc_assert_ret(sw != NULL);
606 fc_assert_ret(GTK_IS_SCROLLED_WINDOW(sw));
607
608 vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
609 val = gtk_adjustment_get_value(vadj);
610 g_object_get(G_OBJECT(vadj), "upper", &upper,
611 "page-size", &page_size, NULL);
612 max = upper - page_size;
613 if (max - val < 10.0) {
614 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), scroll_target,
615 0.0, TRUE, 1.0, 0.0);
616 }
617}
618
619/**********************************************************************/
622 static gboolean event_after(GtkGestureClick *gesture, int n_press,
623 double x, double y, gpointer data)
624{
625 GtkWidget *text_view = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture));
626 GtkTextIter start, end, iter;
627 GtkTextBuffer *buffer;
628 GSList *tags, *tagp;
629 gint bx, by;
630 struct tile *ptile = NULL;
631
632 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
633
634 /* We shouldn't follow a link if the user has selected something. */
635 gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
636 if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) {
637 return FALSE;
638 }
639
640 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW (text_view),
641 GTK_TEXT_WINDOW_WIDGET,
642 x, y, &bx, &by);
643
644 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(text_view), &iter, bx, by);
645
646 if ((tags = gtk_text_iter_get_tags(&iter))) {
647 for (tagp = tags; tagp; tagp = tagp->next) {
648 GtkTextTag *tag = tagp->data;
649 enum text_link_type type =
650 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "type"));
651
652 if (type != 0) {
653 /* This is a link. */
654 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "id"));
655 ptile = NULL;
656
657 /* Real type is type - 1.
658 * See comment in apply_text_tag() for g_object_set_data(). */
659 type--;
660
661 switch (type) {
662 case TLT_CITY:
663 {
664 struct city *pcity = game_city_by_number(id);
665
666 if (pcity) {
667 ptile = client_city_tile(pcity);
668 } else {
669 output_window_append(ftc_client, _("This city isn't known!"));
670 }
671 }
672 break;
673 case TLT_TILE:
674 ptile = index_to_tile(&(wld.map), id);
675
676 if (!ptile) {
677 output_window_append(ftc_client,
678 _("This tile doesn't exist in this game!"));
679 }
680 break;
681 case TLT_UNIT:
682 {
683 struct unit *punit = game_unit_by_number(id);
684
685 if (punit) {
686 ptile = unit_tile(punit);
687 } else {
688 output_window_append(ftc_client, _("This unit isn't known!"));
689 }
690 }
691 break;
692 }
693
694 if (ptile) {
695 center_tile_mapcanvas(ptile);
696 link_mark_restore(type, id);
697 gtk_widget_grab_focus(GTK_WIDGET(map_canvas));
698 }
699 }
700 }
701 g_slist_free(tags);
702 }
703
704 return FALSE;
705}
706
707/**********************************************************************/
710 static void set_cursor_if_appropriate(GtkTextView *text_view, gint x, gint y)
711{
712 static gboolean hovering_over_link = FALSE;
713 static GdkCursor *hand_cursor = NULL;
714 static GdkCursor *regular_cursor = NULL;
715 GSList *tags, *tagp;
716 GtkTextIter iter;
717 gboolean hovering = FALSE;
718
719 /* Initialize the cursors. */
720 if (!hand_cursor) {
721 hand_cursor = gdk_cursor_new_from_name("pointer", NULL);
722 }
723 if (!regular_cursor) {
724 regular_cursor = gdk_cursor_new_from_name("text", NULL);
725 }
726
727 gtk_text_view_get_iter_at_location(text_view, &iter, x, y);
728
729 tags = gtk_text_iter_get_tags(&iter);
730 for (tagp = tags; tagp; tagp = tagp->next) {
731 enum text_link_type type =
732 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tagp->data), "type"));
733
734 if (type != 0) {
735 hovering = TRUE;
736 break;
737 }
738 }
739
740 if (hovering != hovering_over_link) {
741 hovering_over_link = hovering;
742
743 if (hovering_over_link) {
744 gtk_widget_set_cursor(GTK_WIDGET(text_view), hand_cursor);
745 } else {
746 gtk_widget_set_cursor(GTK_WIDGET(text_view), regular_cursor);
747 }
748 }
749
750 if (tags) {
751 g_slist_free(tags);
752 }
753}
754
755/**********************************************************************/
758 static gboolean chat_pointer_motion(GtkEventControllerMotion *controller,
759 gdouble e_x, gdouble e_y, gpointer data)
760{
761 gint x, y;
762 GtkWidget *text_view
763 = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(controller));
764
765 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view),
766 GTK_TEXT_WINDOW_WIDGET,
767 e_x, e_y, &x, &y);
768 set_cursor_if_appropriate(GTK_TEXT_VIEW(text_view), x, y);
769
770 return FALSE;
771}
772
773/**********************************************************************/
776 void set_message_buffer_view_link_handlers(GtkWidget *view)
777{
778 GtkGesture *gesture;
779 GtkEventController *controller;
780
781 gesture = gtk_gesture_click_new();
782 controller = GTK_EVENT_CONTROLLER(gesture);
783 g_signal_connect(controller, "released",
784 G_CALLBACK(event_after), NULL);
785 gtk_widget_add_controller(view, controller);
786
787 controller = GTK_EVENT_CONTROLLER(gtk_event_controller_motion_new());
788 g_signal_connect(controller, "motion",
789 G_CALLBACK(chat_pointer_motion), NULL);
790 gtk_widget_add_controller(view, controller);
791}
792
793/**********************************************************************/
796 void apply_text_tag(const struct text_tag *ptag, GtkTextBuffer *buf,
797 ft_offset_t text_start_offset, const char *text)
798{
799 static bool initialized = FALSE;
800 GtkTextIter start, stop;
801
802 if (!initialized) {
803 gtk_text_buffer_create_tag(buf, "bold",
804 "weight", PANGO_WEIGHT_BOLD, NULL);
805 gtk_text_buffer_create_tag(buf, "italic",
806 "style", PANGO_STYLE_ITALIC, NULL);
807 gtk_text_buffer_create_tag(buf, "strike",
808 "strikethrough", TRUE, NULL);
809 gtk_text_buffer_create_tag(buf, "underline",
810 "underline", PANGO_UNDERLINE_SINGLE, NULL);
811 initialized = TRUE;
812 }
813
814 /* Get the position. */
815 /*
816 * N.B.: text_tag_*_offset() value is in bytes, so we need to convert it
817 * to utf8 character offset.
818 */
819 gtk_text_buffer_get_iter_at_offset(buf, &start, text_start_offset
820 + g_utf8_pointer_to_offset(text,
821 text + text_tag_start_offset(ptag)));
822 if (text_tag_stop_offset(ptag) == FT_OFFSET_UNSET) {
823 gtk_text_buffer_get_end_iter(buf, &stop);
824 } else {
825 gtk_text_buffer_get_iter_at_offset(buf, &stop, text_start_offset
826 + g_utf8_pointer_to_offset(text,
827 text + text_tag_stop_offset(ptag)));
828 }
829
830 switch (text_tag_type(ptag)) {
831 case TTT_BOLD:
832 gtk_text_buffer_apply_tag_by_name(buf, "bold", &start, &stop);
833 break;
834 case TTT_ITALIC:
835 gtk_text_buffer_apply_tag_by_name(buf, "italic", &start, &stop);
836 break;
837 case TTT_STRIKE:
838 gtk_text_buffer_apply_tag_by_name(buf, "strike", &start, &stop);
839 break;
840 case TTT_UNDERLINE:
841 gtk_text_buffer_apply_tag_by_name(buf, "underline", &start, &stop);
842 break;
843 case TTT_COLOR:
844 {
845 /* We have to make a new tag every time. */
846 GtkTextTag *tag = NULL;
847 const char *foreground = text_tag_color_foreground(ptag);
848 const char *background = text_tag_color_background(ptag);
849
850 if (foreground && foreground[0]) {
851 if (background && background[0]) {
852 tag = gtk_text_buffer_create_tag(buf, NULL,
853 "foreground", foreground,
854 "background", background,
855 NULL);
856 } else {
857 tag = gtk_text_buffer_create_tag(buf, NULL,
858 "foreground", foreground,
859 NULL);
860 }
861 } else if (background && background[0]) {
862 tag = gtk_text_buffer_create_tag(buf, NULL,
863 "background", background,
864 NULL);
865 }
866
867 if (!tag) {
868 break; /* No color. */
869 }
870 gtk_text_buffer_apply_tag(buf, tag, &start, &stop);
871 }
872 break;
873 case TTT_LINK:
874 {
875 struct color *pcolor = NULL;
876 GtkTextTag *tag;
877
878 switch (text_tag_link_type(ptag)) {
879 case TLT_CITY:
880 pcolor = get_color(tileset, COLOR_MAPVIEW_CITY_LINK);
881 break;
882 case TLT_TILE:
883 pcolor = get_color(tileset, COLOR_MAPVIEW_TILE_LINK);
884 break;
885 case TLT_UNIT:
886 pcolor = get_color(tileset, COLOR_MAPVIEW_UNIT_LINK);
887 break;
888 }
889
890 if (!pcolor) {
891 break; /* Not a valid link type case. */
892 }
893
894 tag = gtk_text_buffer_create_tag(buf, NULL,
895 "foreground-rgba", &pcolor->color,
896 "underline", PANGO_UNDERLINE_SINGLE,
897 NULL);
898
899 /* Type 0 is reserved for non-link tags. So, add 1 to the
900 * type value. */
901 g_object_set_data(G_OBJECT(tag), "type",
902 GINT_TO_POINTER(text_tag_link_type(ptag) + 1));
903 g_object_set_data(G_OBJECT(tag), "id",
904 GINT_TO_POINTER(text_tag_link_id(ptag)));
905 gtk_text_buffer_apply_tag(buf, tag, &start, &stop);
906 break;
907 }
908 }
909}
910
911/**********************************************************************/
915 void real_output_window_append(const char *astring,
916 const struct text_tag_list *tags,
917 int conn_id)
918{
919 GtkTextBuffer *buf;
920 GtkTextIter iter;
921 GtkTextMark *mark;
922 ft_offset_t text_start_offset;
923
924 buf = message_buffer;
925
926 if (buf == NULL) {
927 log_error("Output when no message buffer: %s", astring);
928
929 return;
930 }
931
932 gtk_text_buffer_get_end_iter(buf, &iter);
933 gtk_text_buffer_insert(buf, &iter, "\n", -1);
934 mark = gtk_text_buffer_create_mark(buf, NULL, &iter, TRUE);
935
936 if (GUI_GTK_OPTION(show_chat_message_time)) {
937 char timebuf[64];
938 time_t now;
939 struct tm now_tm;
940
941 now = time(NULL);
942 fc_localtime(&now, &now_tm);
943 strftime(timebuf, sizeof(timebuf), "[%H:%M:%S] ", &now_tm);
944 gtk_text_buffer_insert(buf, &iter, timebuf, -1);
945 }
946
947 text_start_offset = gtk_text_iter_get_offset(&iter);
948 gtk_text_buffer_insert(buf, &iter, astring, -1);
949 text_tag_list_iterate(tags, ptag) {
950 apply_text_tag(ptag, buf, text_start_offset, astring);
951 } text_tag_list_iterate_end;
952
953 if (main_message_area) {
954 scroll_if_necessary(GTK_TEXT_VIEW(main_message_area), mark);
955 }
956 if (start_message_area) {
957 scroll_if_necessary(GTK_TEXT_VIEW(start_message_area), mark);
958 }
959 gtk_text_buffer_delete_mark(buf, mark);
960
961 append_network_statusbar(astring, FALSE);
962}
963
964/**********************************************************************/
969 void log_output_window(void)
970{
971 GtkTextIter start, end;
972 gchar *txt;
973
974 gtk_text_buffer_get_bounds(message_buffer, &start, &end);
975 txt = gtk_text_buffer_get_text(message_buffer, &start, &end, TRUE);
976
977 write_chatline_content(txt);
978 g_free(txt);
979}
980
981/**********************************************************************/
984 void clear_output_window(void)
985{
986 set_output_window_text(_("Cleared output window."));
987}
988
989/**********************************************************************/
992 void set_output_window_text(const char *text)
993{
994 gtk_text_buffer_set_text(message_buffer, text, -1);
995}
996
997/**********************************************************************/
1000 bool chatline_is_scrolled_to_bottom(void)
1001{
1002 GtkWidget *sw, *w;
1003 GtkAdjustment *vadj;
1004 gdouble val, max, upper, page_size;
1005
1006 if (get_client_page() == PAGE_GAME) {
1007 w = GTK_WIDGET(main_message_area);
1008 } else {
1009 w = GTK_WIDGET(start_message_area);
1010 }
1011
1012 if (w == NULL) {
1013 return TRUE;
1014 }
1015
1016 sw = gtk_widget_get_parent(w);
1017 vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
1018 val = gtk_adjustment_get_value(vadj);
1019 g_object_get(G_OBJECT(vadj), "upper", &upper,
1020 "page-size", &page_size, NULL);
1021 max = upper - page_size;
1022
1023 /* Approximation. */
1024 return max - val < 0.00000001;
1025}
1026
1027/**********************************************************************/
1035 static gboolean chatline_scroll_callback(gpointer data)
1036{
1037 chatline_scroll_to_bottom(FALSE); /* Not delayed this time! */
1038
1039 *((guint *) data) = 0;
1040
1041 return FALSE; /* Remove this idle function. */
1042}
1043
1044/**********************************************************************/
1048 void chatline_scroll_to_bottom(bool delayed)
1049{
1050 static guint callback_id = 0;
1051
1052 if (delayed) {
1053 if (callback_id == 0) {
1054 callback_id = g_idle_add(chatline_scroll_callback, &callback_id);
1055 }
1056 } else if (message_buffer) {
1057 GtkTextIter end;
1058
1059 gtk_text_buffer_get_end_iter(message_buffer, &end);
1060
1061 if (main_message_area) {
1062 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_message_area),
1063 &end, 0.0, TRUE, 1.0, 0.0);
1064 }
1065 if (start_message_area) {
1066 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(start_message_area),
1067 &end, 0.0, TRUE, 1.0, 0.0);
1068 }
1069 }
1070}
1071
1072/**********************************************************************/
1075 static void make_tag_callback(GtkButton *button, gpointer data)
1076{
1077 inputline_make_tag(GTK_ENTRY(data),
1078 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button),
1079 "text_tag_type")));
1080}
1081
1082/**********************************************************************/
1085 static void color_set(GObject *object, const gchar *color_target,
1086 GdkRGBA *color, GtkButton *button)
1087{
1088 GdkRGBA *current_color = g_object_get_data(object, color_target);
1089
1090 if (NULL == color) {
1091 /* Clears the current color. */
1092 if (NULL != current_color) {
1093 gdk_rgba_free(current_color);
1094 g_object_set_data(object, color_target, NULL);
1095 if (NULL != button) {
1096 gtk_button_set_child(button, NULL);
1097 }
1098 }
1099 } else {
1100 /* Apply the new color. */
1101 if (NULL != current_color) {
1102 /* We already have a GdkRGBA pointer. */
1103 *current_color = *color;
1104 } else {
1105 /* We need to make a GdkRGBA pointer. */
1106 current_color = gdk_rgba_copy(color);
1107 g_object_set_data(object, color_target, current_color);
1108 }
1109
1110 if (NULL != button) {
1111 /* Update the button. */
1112 GdkPixbuf *pixbuf;
1113 GtkWidget *image;
1114
1115 gtk_button_set_child(button, NULL);
1116
1117 {
1118 cairo_surface_t *surface = cairo_image_surface_create(
1119 CAIRO_FORMAT_RGB24, 16, 16);
1120 cairo_t *cr = cairo_create(surface);
1121
1122 gdk_cairo_set_source_rgba(cr, current_color);
1123 cairo_paint(cr);
1124 cairo_destroy(cr);
1125 pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, 16, 16);
1126 cairo_surface_destroy(surface);
1127 }
1128 image = gtk_image_new_from_pixbuf(pixbuf);
1129 gtk_button_set_child(button, image);
1130 gtk_widget_set_visible(image, TRUE);
1131 g_object_unref(G_OBJECT(pixbuf));
1132 }
1133 }
1134}
1135
1136/**********************************************************************/
1139 static void color_selected(GtkDialog *dialog, gint res, gpointer data)
1140{
1141 const gchar *color_target =
1142 g_object_get_data(G_OBJECT(data), "color_target");
1143 GObject *entry = g_object_get_data(G_OBJECT(data), "entry");
1144
1145 if (res == GTK_RESPONSE_REJECT) {
1146 /* Clears the current color. */
1147 color_set(entry, color_target, NULL, data);
1148 } else if (res == GTK_RESPONSE_OK) {
1149 /* Apply the new color. */
1150 GtkColorChooser *chooser
1151 = GTK_COLOR_CHOOSER(g_object_get_data(G_OBJECT(dialog), "chooser"));
1152 GdkRGBA new_color;
1153
1154 gtk_color_chooser_get_rgba(chooser, &new_color);
1155 color_set(entry, color_target, &new_color, data);
1156 }
1157
1158 gtk_window_destroy(GTK_WINDOW(dialog));
1159}
1160
1161/**********************************************************************/
1164 static void select_color_callback(GtkButton *button, gpointer data)
1165{
1166 GtkWidget *dialog, *chooser;
1167 /* "fg_color" or "bg_color". */
1168 const gchar *color_target = g_object_get_data(G_OBJECT(button),
1169 "color_target");
1170 GdkRGBA *current_color = g_object_get_data(G_OBJECT(data), color_target);
1171
1172 /* TRANS: "text" or "background". */
1173 gchar *buf = g_strdup_printf(_("Select the %s color"),
1174 (const char *) g_object_get_data(G_OBJECT(button),
1175 "color_info"));
1176 dialog = gtk_dialog_new_with_buttons(buf, NULL, GTK_DIALOG_MODAL,
1177 _("_Cancel"), GTK_RESPONSE_CANCEL,
1178 _("C_lear"), GTK_RESPONSE_REJECT,
1179 _("_OK"), GTK_RESPONSE_OK, NULL);
1180 setup_dialog(dialog, toplevel);
1181 g_object_set_data(G_OBJECT(button), "entry", data);
1182 g_signal_connect(dialog, "response", G_CALLBACK(color_selected), button);
1183
1184 chooser = gtk_color_chooser_widget_new();
1185 gtk_box_insert_child_after(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
1186 chooser, NULL);
1187 g_object_set_data(G_OBJECT(dialog), "chooser", chooser);
1188
1189 if (current_color != nullptr) {
1190 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(chooser), current_color);
1191 }
1192
1193 gtk_widget_set_visible(dialog, TRUE);
1194 g_free(buf);
1195}
1196
1197/**********************************************************************/
1200 static gboolean move_toolkit(GtkWidget *toolkit_view, gpointer data)
1201{
1202 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1203 GtkWidget *parent = gtk_widget_get_parent(ptoolkit->main_widget);
1204 GtkWidget *button_box = GTK_WIDGET(g_object_get_data(G_OBJECT(toolkit_view),
1205 "button_box"));
1206 GtkWidget *iter;
1207
1208 if (parent) {
1209 if (parent == toolkit_view) {
1210 return FALSE; /* Already owned. */
1211 }
1212
1213 /* N.B.: We need to hide/show the toolbar to reset the sensitivity
1214 * of the tool buttons. */
1215 if (ptoolkit->toolbar_displayed) {
1216 gtk_widget_set_visible(ptoolkit->toolbar, FALSE);
1217 }
1218 g_object_ref(ptoolkit->main_widget); /* Make sure reference count stays above 0
1219 * during the transition to new parent. */
1220 gtk_box_remove(GTK_BOX(parent), ptoolkit->main_widget);
1221 gtk_box_append(GTK_BOX(toolkit_view), ptoolkit->main_widget);
1222 g_object_unref(ptoolkit->main_widget);
1223 if (ptoolkit->toolbar_displayed) {
1224 gtk_widget_set_visible(ptoolkit->toolbar, TRUE);
1225 }
1226
1227 if (!gtk_widget_get_parent(button_box)) {
1228 /* Attach to the toolkit button_box. */
1229 gtk_box_append(GTK_BOX(ptoolkit->button_box), button_box);
1230 }
1231 gtk_widget_set_visible(button_box, TRUE);
1232 if (!ptoolkit->toolbar_displayed) {
1233 gtk_widget_set_visible(ptoolkit->toolbar, FALSE);
1234 }
1235
1236 /* Hide all other buttons boxes. */
1237 for (iter = gtk_widget_get_first_child(GTK_WIDGET(ptoolkit->button_box));
1238 iter != nullptr;
1239 iter = gtk_widget_get_next_sibling(iter)) {
1240 if (iter != button_box) {
1241 gtk_widget_set_visible(iter, FALSE);
1242 }
1243 }
1244
1245 } else {
1246 /* First time attached to a parent. */
1247 gtk_box_append(GTK_BOX(toolkit_view), ptoolkit->main_widget);
1248 gtk_box_append(GTK_BOX(ptoolkit->button_box), button_box);
1249 gtk_widget_set_visible(ptoolkit->main_widget, TRUE);
1250 }
1251
1252 return FALSE;
1253}
1254
1255/**********************************************************************/
1258 static gboolean set_toolbar_visibility(GtkWidget *w, gpointer data)
1259{
1260 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1261 GtkToggleButton *button = GTK_TOGGLE_BUTTON(toolkit.toggle_button);
1262
1263 if (ptoolkit->toolbar_displayed) {
1264 if (!gtk_toggle_button_get_active(button)) {
1265 /* button_toggled() will be called and the toolbar shown. */
1266 gtk_toggle_button_set_active(button, TRUE);
1267 } else {
1268 /* Ensure the widget is visible. */
1269 gtk_widget_set_visible(ptoolkit->toolbar, TRUE);
1270 }
1271 } else {
1272 if (gtk_toggle_button_get_active(button)) {
1273 /* button_toggled() will be called and the toolbar hidden. */
1274 gtk_toggle_button_set_active(button, FALSE);
1275 } else {
1276 /* Ensure the widget is not visible. */
1277 gtk_widget_set_visible(ptoolkit->toolbar, FALSE);
1278 }
1279 }
1280
1281 return FALSE;
1282}
1283
1284/**********************************************************************/
1287 static void button_toggled(GtkToggleButton *button, gpointer data)
1288{
1289 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1290
1291 if (gtk_toggle_button_get_active(button)) {
1292 gtk_widget_set_visible(ptoolkit->toolbar, TRUE);
1293 ptoolkit->toolbar_displayed = TRUE;
1294 if (chatline_is_scrolled_to_bottom()) {
1295 /* Make sure to be still at the end. */
1296 chatline_scroll_to_bottom(TRUE);
1297 }
1298 } else {
1299 gtk_widget_set_visible(ptoolkit->toolbar, FALSE);
1300 ptoolkit->toolbar_displayed = FALSE;
1301 }
1302}
1303
1304/**********************************************************************/
1311 GtkWidget *inputline_toolkit_view_new(void)
1312{
1313 GtkWidget *toolkit_view, *bbox;
1314
1315 /* Main widget. */
1316 toolkit_view = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
1317 g_signal_connect_after(toolkit_view, "map",
1318 G_CALLBACK(move_toolkit), &toolkit);
1319
1320 /* Button box. */
1321 bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
1322 g_object_set_data(G_OBJECT(toolkit_view), "button_box", bbox);
1323
1324 return toolkit_view;
1325}
1326
1327/**********************************************************************/
1330 void inputline_toolkit_view_append_button(GtkWidget *toolkit_view,
1331 GtkWidget *button)
1332{
1333 gtk_box_append(GTK_BOX(g_object_get_data(G_OBJECT(toolkit_view),
1334 "button_box")), button);
1335}
1336
1337/**********************************************************************/
1340 void chatline_init(void)
1341{
1342 GtkWidget *vbox, *hgrid, *entry, *bbox;
1343 GtkWidget *button;
1344 GtkWidget *toolbar;
1345 GtkWidget *item;
1346 GdkRGBA color;
1347 int grid_col = 0;
1348 GtkEventController *chat_controller;
1349 GtkEventController *focus_controller;
1350
1351 /* Chatline history. */
1352 if (!history_list) {
1353 history_list = genlist_new();
1354 history_pos = -1;
1355 }
1356
1357 /* Inputline toolkit. */
1358 memset(&toolkit, 0, sizeof(toolkit));
1359
1360 vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
1361
1362 toolkit.main_widget = vbox;
1363 g_signal_connect_after(vbox, "map",
1364 G_CALLBACK(set_toolbar_visibility), &toolkit);
1365
1366 entry = gtk_entry_new();
1367 gtk_widget_set_margin_bottom(entry, 2);
1368 gtk_widget_set_margin_end(entry, 2);
1369 gtk_widget_set_margin_start(entry, 2);
1370 gtk_widget_set_margin_top(entry, 2);
1371 gtk_widget_set_hexpand(entry, TRUE);
1372 focus_controller = GTK_EVENT_CONTROLLER(gtk_event_controller_focus_new());
1373 g_signal_connect(focus_controller, "enter",
1374 G_CALLBACK(il_gained_focus), NULL);
1375 g_signal_connect(focus_controller, "leave",
1376 G_CALLBACK(il_lost_focus), NULL);
1377 gtk_widget_add_controller(entry, focus_controller);
1378 toolkit.entry = entry;
1379
1380 hgrid = gtk_grid_new();
1381 gtk_grid_set_column_spacing(GTK_GRID(hgrid), 4);
1382
1383 /* First line: toolbar */
1384 toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1385 gtk_box_append(GTK_BOX(vbox), toolbar);
1386 toolkit.toolbar = toolbar;
1387
1388 /* Bold button. */
1389 item = gtk_button_new_from_icon_name("format-text-bold");
1390
1391 /* _("Bold")); */
1392
1393 gtk_box_append(GTK_BOX(toolbar), item);
1394 g_object_set_data(G_OBJECT(item), "text_tag_type",
1395 GINT_TO_POINTER(TTT_BOLD));
1396 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1397 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Bold (Ctrl-B)"));
1398
1399 /* Italic button. */
1400 item = gtk_button_new_from_icon_name("format-text-italic");
1401
1402 /* _("Italic")); */
1403
1404 gtk_box_append(GTK_BOX(toolbar), item);
1405 g_object_set_data(G_OBJECT(item), "text_tag_type",
1406 GINT_TO_POINTER(TTT_ITALIC));
1407 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1408 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Italic (Ctrl-I)"));
1409
1410 /* Strike button. */
1411 item = gtk_button_new_from_icon_name("format-text-strikethrough");
1412
1413 /* _("Strikethrough")); */
1414 gtk_box_append(GTK_BOX(toolbar), item);
1415 g_object_set_data(G_OBJECT(item), "text_tag_type",
1416 GINT_TO_POINTER(TTT_STRIKE));
1417 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1418 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Strikethrough (Ctrl-S)"));
1419
1420 /* Underline button. */
1421 item = gtk_button_new_from_icon_name("format-text-underline");
1422
1423 /* _("Underline")); */
1424 gtk_box_append(GTK_BOX(toolbar), item);
1425 g_object_set_data(G_OBJECT(item), "text_tag_type",
1426 GINT_TO_POINTER(TTT_UNDERLINE));
1427 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1428 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Underline (Ctrl-U)"));
1429
1430 gtk_box_append(GTK_BOX(toolbar), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
1431
1432 /* Color button. */
1433 item = gtk_button_new_with_label(_("Color"));
1434
1435 gtk_box_append(GTK_BOX(toolbar), item);
1436 g_object_set_data(G_OBJECT(item), "text_tag_type",
1437 GINT_TO_POINTER(TTT_COLOR));
1438 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1439 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Color (Ctrl-C)"));
1440
1441 gtk_box_append(GTK_BOX(toolbar), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
1442
1443 /* Foreground selector. */
1444 item = gtk_button_new();
1445 gtk_box_append(GTK_BOX(toolbar), item);
1446 g_object_set_data(G_OBJECT(item), "color_target", fc_strdup("fg_color"));
1447 g_object_set_data(G_OBJECT(item), "color_info",
1448 fc_strdup(_("foreground")));
1449 g_signal_connect(item, "clicked",
1450 G_CALLBACK(select_color_callback), entry);
1451 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Select the text color"));
1452 if (gdk_rgba_parse(&color, "#000000")) {
1453 /* Set default foreground color. */
1454 color_set(G_OBJECT(entry), "fg_color", &color, GTK_BUTTON(item));
1455 } else {
1456 log_error("Failed to set the default foreground color.");
1457 }
1458
1459 /* Background selector. */
1460 item = gtk_button_new();
1461 gtk_box_append(GTK_BOX(toolbar), item);
1462 g_object_set_data(G_OBJECT(item), "color_target", fc_strdup("bg_color"));
1463 g_object_set_data(G_OBJECT(item), "color_info",
1464 fc_strdup(_("background")));
1465 g_signal_connect(item, "clicked",
1466 G_CALLBACK(select_color_callback), entry);
1467 gtk_widget_set_tooltip_text(GTK_WIDGET(item),
1468 _("Select the background color"));
1469 if (gdk_rgba_parse(&color, "#ffffff")) {
1470 /* Set default background color. */
1471 color_set(G_OBJECT(entry), "bg_color", &color, GTK_BUTTON(item));
1472 } else {
1473 log_error("Failed to set the default background color.");
1474 }
1475
1476 gtk_box_append(GTK_BOX(toolbar), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL));
1477
1478 /* Return button. */
1479 item = gtk_button_new_with_label(_("OK"));
1480 gtk_box_append(GTK_BOX(toolbar), item);
1481 g_signal_connect_swapped(item, "clicked",
1482 G_CALLBACK(inputline_return), entry);
1483 gtk_widget_set_tooltip_text(GTK_WIDGET(item),
1484 /* TRANS: "Return" means the return key. */
1485 _("Send the chat (Return)"));
1486
1487 /* Second line */
1488 gtk_box_append(GTK_BOX(vbox), hgrid);
1489
1490 /* Toggle button. */
1491 button = gtk_toggle_button_new();
1492 gtk_widget_set_margin_bottom(button, 2);
1493 gtk_widget_set_margin_end(button, 2);
1494 gtk_widget_set_margin_start(button, 2);
1495 gtk_widget_set_margin_top(button, 2);
1496 gtk_grid_attach(GTK_GRID(hgrid), button, grid_col++, 0, 1, 1);
1497 gtk_button_set_icon_name(GTK_BUTTON(button), "insert-link");
1498 g_signal_connect(button, "toggled", G_CALLBACK(button_toggled), &toolkit);
1499 gtk_widget_set_tooltip_text(GTK_WIDGET(button), _("Chat tools"));
1500 toolkit.toggle_button = button;
1501
1502 /* Entry. */
1503 gtk_grid_attach(GTK_GRID(hgrid), entry, grid_col++, 0, 1, 1);
1504 g_signal_connect(entry, "activate", G_CALLBACK(inputline_return), NULL);
1505
1506 chat_controller = gtk_event_controller_key_new();
1507 g_signal_connect(chat_controller, "key-pressed",
1508 G_CALLBACK(inputline_handler), NULL);
1509 gtk_widget_add_controller(entry, chat_controller);
1510
1511 /* Button box. */
1512 bbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
1513 gtk_grid_attach(GTK_GRID(hgrid), bbox, grid_col++, 0, 1, 1);
1514 toolkit.button_box = bbox;
1515}
1516
1517/**********************************************************************/
1520 static gboolean version_message_main_thread(gpointer user_data)
1521{
1522 char *vertext = (char *)user_data;
1523
1524 output_window_append(ftc_client, vertext);
1525
1526 FC_FREE(vertext);
1527
1528 return G_SOURCE_REMOVE;
1529}
1530
1531/**********************************************************************/
1534 void version_message(const char *vertext)
1535{
1536 int len = strlen(vertext) + 1;
1537 char *persistent = fc_malloc(len);
1538
1539 strncpy(persistent, vertext, len);
1540
1541 g_idle_add(version_message_main_thread, persistent);
1542}
pcolor
struct canvas int int int int struct sprite *sprite struct canvas struct color * pcolor
Definition canvas_g.h:56
chat.h
CHAT_DIRECT_PREFIX
#define CHAT_DIRECT_PREFIX
Definition chat.h:31
CHAT_ALLIES_PREFIX
#define CHAT_ALLIES_PREFIX
Definition chat.h:30
SERVER_COMMAND_PREFIX
#define SERVER_COMMAND_PREFIX
Definition chat.h:28
send_chat
int send_chat(const char *message)
Definition chatline_common.c:46
output_window_append
void output_window_append(const struct ft_color color, const char *featured_text)
Definition chatline_common.c:104
write_chatline_content
void write_chatline_content(const char *txt)
Definition chatline_common.c:196
output_window_printf
void output_window_printf(const struct ft_color color, const char *format,...)
Definition chatline_common.c:153
client_state
enum client_states client_state(void)
Definition client_main.c:1034
client_main.h
C_S_RUNNING
@ C_S_RUNNING
Definition client_main.h:47
client_city_tile
struct tile * client_city_tile(const struct city *pcity)
Definition climap.c:87
climap.h
get_color
struct color * get_color(const struct tileset *t, enum color_std stdcolor)
Definition colors_common.c:100
incite_cost
char * incite_cost
Definition comments.c:74
MAX_LEN_MSG
#define MAX_LEN_MSG
Definition conn_types.h:37
find_visible_unit
struct unit * find_visible_unit(struct tile *ptile)
Definition control.c:804
control.h
punit
struct unit struct city struct unit struct tile struct extra_type const struct act_prob *act_probs int actor_unit_id struct unit struct unit * punit
Definition dialogs_g.h:74
initialized
static bool initialized
Definition effects.c:42
MAX_LEN_NAME
#define MAX_LEN_NAME
Definition fc_types.h:66
fcintl.h
_
#define _(String)
Definition fcintl.h:67
featured_text_apply_tag
size_t featured_text_apply_tag(const char *text_source, char *featured_text, size_t featured_text_len, enum text_tag_type tag_type, ft_offset_t start_offset, ft_offset_t stop_offset,...)
Definition featured_text.c:997
text_tag_link_type
enum text_link_type text_tag_link_type(const struct text_tag *ptag)
Definition featured_text.c:745
tile_link
const char * tile_link(const struct tile *ptile)
Definition featured_text.c:1110
text_tag_stop_offset
ft_offset_t text_tag_stop_offset(const struct text_tag *ptag)
Definition featured_text.c:708
text_tag_color_foreground
const char * text_tag_color_foreground(const struct text_tag *ptag)
Definition featured_text.c:717
ftc_client
const struct ft_color ftc_client
Definition featured_text.c:78
city_link
const char * city_link(const struct city *pcity)
Definition featured_text.c:1076
text_tag_link_id
int text_tag_link_id(const struct text_tag *ptag)
Definition featured_text.c:760
text_tag_start_offset
ft_offset_t text_tag_start_offset(const struct text_tag *ptag)
Definition featured_text.c:700
unit_link
const char * unit_link(const struct unit *punit)
Definition featured_text.c:1126
text_tag_color_background
const char * text_tag_color_background(const struct text_tag *ptag)
Definition featured_text.c:731
featured_text.h
text_tag_list_iterate_end
#define text_tag_list_iterate_end
Definition featured_text.h:116
text_tag_list_iterate
#define text_tag_list_iterate(tags, ptag)
Definition featured_text.h:114
FT_OFFSET_UNSET
#define FT_OFFSET_UNSET
Definition featured_text.h:104
ft_offset_t
int ft_offset_t
Definition featured_text.h:102
text_link_type
text_link_type
Definition featured_text.h:131
TLT_TILE
@ TLT_TILE
Definition featured_text.h:133
TLT_UNIT
@ TLT_UNIT
Definition featured_text.h:134
TLT_CITY
@ TLT_CITY
Definition featured_text.h:132
text_tag_type
text_tag_type
Definition featured_text.h:120
TTT_LINK
@ TTT_LINK
Definition featured_text.h:126
TTT_BOLD
@ TTT_BOLD
Definition featured_text.h:121
TTT_ITALIC
@ TTT_ITALIC
Definition featured_text.h:122
TTT_STRIKE
@ TTT_STRIKE
Definition featured_text.h:123
TTT_COLOR
@ TTT_COLOR
Definition featured_text.h:125
TTT_UNDERLINE
@ TTT_UNDERLINE
Definition featured_text.h:124
ft_color_construct
static struct ft_color ft_color_construct(const char *foreground, const char *background)
Definition featured_text.h:149
game
struct civ_game game
Definition game.c:61
wld
struct world wld
Definition game.c:62
game_unit_by_number
struct unit * game_unit_by_number(int id)
Definition game.c:115
game_city_by_number
struct city * game_city_by_number(int id)
Definition game.c:106
game.h
genlist_remove
bool genlist_remove(struct genlist *pgenlist, const void *punlink)
Definition genlist.c:322
genlist_new
struct genlist * genlist_new(void)
Definition genlist.c:31
genlist_get
void * genlist_get(const struct genlist *pgenlist, int idx)
Definition genlist.c:221
genlist_prepend
void genlist_prepend(struct genlist *pgenlist, void *data)
Definition genlist.c:500
genlist_size
int genlist_size(const struct genlist *pgenlist)
Definition genlist.c:192
genlist.h
inputline_has_focus
bool inputline_has_focus(void)
Definition chatline.c:72
set_cursor_if_appropriate
static void set_cursor_if_appropriate(GtkTextView *text_view, gint x, gint y)
Definition chatline.c:675
get_common_prefix
static size_t get_common_prefix(const char *const *prefixes, size_t num_prefixes, char *buf, size_t buf_len)
Definition chatline.c:258
history_pos
static int history_pos
Definition chatline.c:56
MAX_CHATLINE_HISTORY
#define MAX_CHATLINE_HISTORY
Definition chatline.c:53
version_message_main_thread
static gboolean version_message_main_thread(gpointer user_data)
Definition chatline.c:1464
inputline_toolkit_view_append_button
void inputline_toolkit_view_append_button(GtkWidget *toolkit_view, GtkWidget *button)
Definition chatline.c:1293
version_message
void version_message(const char *vertext)
Definition chatline.c:1478
get_player_or_user_name
static const char * get_player_or_user_name(int id)
Definition chatline.c:176
inputline_toolkit_view_new
GtkWidget * inputline_toolkit_view_new(void)
Definition chatline.c:1271
log_output_window
void log_output_window(void)
Definition chatline.c:929
chatline_scroll_callback
static gboolean chatline_scroll_callback(gpointer data)
Definition chatline.c:995
set_message_buffer_view_link_handlers
void set_message_buffer_view_link_handlers(GtkWidget *view)
Definition chatline.c:745
button_toggled
static void button_toggled(GtkToggleButton *button, gpointer data)
Definition chatline.c:1247
select_color_callback
static void select_color_callback(GtkToolButton *button, gpointer data)
Definition chatline.c:1121
scroll_if_necessary
void scroll_if_necessary(GtkTextView *textview, GtkTextMark *scroll_target)
Definition chatline.c:558
inputline_is_visible
bool inputline_is_visible(void)
Definition chatline.c:88
color_selected
static void color_selected(GtkDialog *dialog, gint res, gpointer data)
Definition chatline.c:1095
is_plain_public_message
static bool is_plain_public_message(const char *s)
Definition chatline.c:98
chatline_autocomplete
static bool chatline_autocomplete(GtkEditable *editable)
Definition chatline.c:285
history_list
static struct genlist * history_list
Definition chatline.c:55
clear_output_window
void clear_output_window(void)
Definition chatline.c:944
apply_text_tag
void apply_text_tag(const struct text_tag *ptag, GtkTextBuffer *buf, ft_offset_t text_start_offset, const char *text)
Definition chatline.c:756
chatline_init
void chatline_init(void)
Definition chatline.c:1303
make_tag_callback
static void make_tag_callback(GtkToolButton *button, gpointer data)
Definition chatline.c:1034
check_player_or_user_name
static int check_player_or_user_name(const char *prefix, const char **matches, const int max_matches)
Definition chatline.c:203
toolkit
static struct inputline_toolkit toolkit
inputline_make_chat_link
void inputline_make_chat_link(struct tile *ptile, bool unit)
Definition chatline.c:475
chatline_is_scrolled_to_bottom
bool chatline_is_scrolled_to_bottom(void)
Definition chatline.c:960
event_after
static gboolean event_after(GtkWidget *text_view, GdkEventButton *event)
Definition chatline.c:585
real_output_window_append
void real_output_window_append(const char *astring, const struct text_tag_list *tags, int conn_id)
Definition chatline.c:875
set_toolbar_visibility
static gboolean set_toolbar_visibility(GtkWidget *w, gpointer data)
Definition chatline.c:1217
inputline_handler
static gboolean inputline_handler(GtkWidget *w, GdkEventKey *ev)
Definition chatline.c:346
inputline_return
static void inputline_return(GtkEntry *w, gpointer data)
Definition chatline.c:140
inputline_grab_focus
void inputline_grab_focus(void)
Definition chatline.c:80
chatline_scroll_to_bottom
void chatline_scroll_to_bottom(bool delayed)
Definition chatline.c:1007
MAX_MATCHES
#define MAX_MATCHES
move_toolkit
static gboolean move_toolkit(GtkWidget *toolkit_view, gpointer data)
Definition chatline.c:1156
inputline_make_tag
static void inputline_make_tag(GtkEntry *entry, enum text_tag_type type)
Definition chatline.c:417
pos
static struct tile * pos
Definition finddlg.c:53
message_buffer
GtkTextBuffer * message_buffer
Definition gui_main.c:178
toplevel
GtkWidget * toplevel
Definition gui_main.c:125
map_canvas
GtkWidget * map_canvas
Definition gui_main.c:107
main_message_area
GtkTextView * main_message_area
Definition gui_main.c:177
GUI_GTK_OPTION
#define GUI_GTK_OPTION(optname)
Definition gui_main.h:25
setup_dialog
void setup_dialog(GtkWidget *shell, GtkWidget *parent)
Definition gui_stuff.c:287
real_menus_update
void real_menus_update(void)
Definition menu.c:2353
append_network_statusbar
void append_network_statusbar(const char *text, bool force)
Definition pages.c:892
start_message_area
GtkWidget * start_message_area
Definition pages.c:1455
persistent
static GtkWidget * persistent
Definition rallypointdlg.c:40
type
GType type
Definition repodlgs.c:1313
il_lost_focus
static gboolean il_lost_focus(GtkEventControllerFocus *controller, gpointer data)
Definition chatline.c:97
chat_pointer_motion
static gboolean chat_pointer_motion(GtkEventControllerMotion *controller, gdouble e_x, gdouble e_y, gpointer data)
Definition chatline.c:758
il_gained_focus
static gboolean il_gained_focus(GtkEventControllerFocus *controller, gpointer data)
Definition chatline.c:108
menus_disable_unit_commands
void menus_disable_unit_commands(void)
Definition menu.c:4151
gui_stuff.h
set_output_window_text
#define set_output_window_text(_pstr_)
Definition chatline.h:31
chatline.h
colors.h
gui_main.h
menu.h
pages.h
name
const char * name
Definition inputfile.c:127
log.h
fc_assert_ret
#define fc_assert_ret(condition)
Definition log.h:191
log_error
#define log_error(message,...)
Definition log.h:103
index_to_tile
struct tile * index_to_tile(const struct civ_map *imap, int mindex)
Definition map.c:462
link_mark_restore
void link_mark_restore(enum text_link_type type, int id)
Definition mapview_common.c:3979
center_tile_mapcanvas
void center_tile_mapcanvas(const struct tile *ptile)
Definition mapview_common.c:1237
mapview_common.h
mem.h
FC_FREE
#define FC_FREE(ptr)
Definition mem.h:41
fc_strdup
#define fc_strdup(str)
Definition mem.h:43
fc_malloc
#define fc_malloc(sz)
Definition mem.h:34
color_set
#define color_set(color_tgt, color)
packets.h
len
int len
Definition packhand.c:127
player_by_number
struct player * player_by_number(const int player_id)
Definition player.c:849
player_slot_count
int player_slot_count(void)
Definition player.c:418
match_prefix_full
enum m_pre_result match_prefix_full(m_pre_accessor_fn_t accessor_fn, size_t n_names, size_t max_len_name, m_pre_strncmp_fn_t cmp_fn, m_strlen_fn_t len_fn, const char *prefix, int *ind_result, int *matches, int max_matches, int *pnum_matches)
Definition shared.c:1603
MAX
#define MAX(x, y)
Definition shared.h:54
M_PRE_EXACT
@ M_PRE_EXACT
Definition shared.h:214
M_PRE_ONLY
@ M_PRE_ONLY
Definition shared.h:215
M_PRE_LAST
@ M_PRE_LAST
Definition shared.h:220
M_PRE_LONG
@ M_PRE_LONG
Definition shared.h:218
M_PRE_AMBIGUOUS
@ M_PRE_AMBIGUOUS
Definition shared.h:216
M_PRE_EMPTY
@ M_PRE_EMPTY
Definition shared.h:217
M_PRE_FAIL
@ M_PRE_FAIL
Definition shared.h:219
size
size_t size
Definition specvec.h:72
y
struct sprite int int y
Definition sprite_g.h:31
x
struct sprite int x
Definition sprite_g.h:31
astring
Definition astring.h:36
city
Definition city.h:320
civ_game::all_connections
struct conn_list * all_connections
Definition game.h:96
color
Definition colors.h:21
entry
Definition registry_ini.c:193
genlist
Definition genlist.h:59
inputline_toolkit
Definition chatline.c:58
inputline_toolkit::main_widget
GtkWidget * main_widget
Definition chatline.c:59
inputline_toolkit::toggle_button
GtkWidget * toggle_button
Definition chatline.c:63
inputline_toolkit::button_box
GtkWidget * button_box
Definition chatline.c:61
inputline_toolkit::entry
GtkWidget * entry
Definition chatline.c:60
inputline_toolkit::toolbar
GtkWidget * toolbar
Definition chatline.c:62
inputline_toolkit::toolbar_displayed
bool toolbar_displayed
Definition chatline.c:64
item
Definition climisc.h:80
player
Definition player.h:249
player::name
char name[MAX_LEN_NAME]
Definition player.h:251
text_tag
Definition featured_text.c:49
tile
Definition tile.h:50
tileset
Definition tilespec.c:482
toolkit
Definition mapimg.c:419
unit
Definition unit.h:140
view
Definition mapview_common.h:36
world::map
struct civ_map map
Definition world_object.h:38
fc_snprintf
int fc_snprintf(char *str, size_t n, const char *format,...)
Definition support.c:960
fc_strlcpy
size_t fc_strlcpy(char *dest, const char *src, size_t n)
Definition support.c:777
fc_isspace
bool fc_isspace(char c)
Definition support.c:1240
fc_localtime
struct tm * fc_localtime(const time_t *timep, struct tm *result)
Definition support.c:1301
cat_snprintf
int cat_snprintf(char *str, size_t n, const char *format,...)
Definition support.c:986
fc_strncasecmp
int fc_strncasecmp(const char *str0, const char *str1, size_t n)
Definition support.c:235
support.h
sz_strlcpy
#define sz_strlcpy(dest, src)
Definition support.h:189
TRUE
#define TRUE
Definition support.h:46
FALSE
#define FALSE
Definition support.h:47
tile_city
struct city * tile_city(const struct tile *ptile)
Definition tile.c:83
unit_tile
#define unit_tile(_pu)
Definition unit.h:397
get_client_page
enum client_pages get_client_page(void)
Definition update_queue.c:520
update_queue.h