Freeciv-3.1: /fast/freeciv/nightly/S3_1/client/gui-gtk-3.0/chatline.c Source File

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