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