Qt client - remove right click menu from end turn sidebar
[freeciv.git] / client / gui-gtk-2.0 / chatline.c
blobb8fa5937173d7551732d56bb451de6cbdfe4ac83
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.
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 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <time.h>
23 #include <gdk/gdkkeysyms.h>
25 /* utility */
26 #include "fcintl.h"
27 #include "genlist.h"
28 #include "log.h"
29 #include "mem.h"
30 #include "support.h"
32 /* common */
33 #include "chat.h"
34 #include "featured_text.h"
35 #include "game.h"
36 #include "packets.h"
38 /* client */
39 #include "client_main.h"
40 #include "climap.h"
41 #include "control.h"
42 #include "mapview_common.h"
44 /* gui-gtk-2.0 */
45 #include "colors.h"
46 #include "gui_main.h"
47 #include "gui_stuff.h"
48 #include "pages.h"
50 #include "chatline.h"
52 #define MAX_CHATLINE_HISTORY 20
54 static struct genlist *history_list = NULL;
55 static int history_pos = -1;
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. */
66 static void inputline_make_tag(GtkEntry *entry, enum text_tag_type type);
68 /**************************************************************************
69 Returns TRUE iff the input line has focus.
70 **************************************************************************/
71 bool inputline_has_focus(void)
73 return GTK_WIDGET_HAS_FOCUS(toolkit.entry);
76 /**************************************************************************
77 Gives the focus to the intput line.
78 **************************************************************************/
79 void inputline_grab_focus(void)
81 gtk_widget_grab_focus(toolkit.entry);
84 /**************************************************************************
85 Returns TRUE iff the input line is currently visible.
86 **************************************************************************/
87 bool inputline_is_visible(void)
89 return GTK_WIDGET_MAPPED(toolkit.entry);
92 /**************************************************************************
93 Helper function to determine if a given client input line is intended as
94 a "plain" public message. Note that messages prefixed with : are a
95 special case (explicit public messages), and will return FALSE.
96 **************************************************************************/
97 static bool is_plain_public_message(const char *s)
99 const char *p;
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;
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;
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;
131 p++;
133 return TRUE;
137 /**************************************************************************
138 Called when the return key is pressed.
139 **************************************************************************/
140 static void inputline_return(GtkEntry *w, gpointer data)
142 const char *theinput;
144 theinput = gtk_entry_get_text(w);
146 if (*theinput) {
147 if (client_state() == C_S_RUNNING
148 && gui_options.gui_gtk2_allied_chat_only
149 && is_plain_public_message(theinput)) {
150 char buf[MAX_LEN_MSG];
152 fc_snprintf(buf, sizeof(buf), ". %s", theinput);
153 send_chat(buf);
154 } else {
155 send_chat(theinput);
158 if (genlist_size(history_list) >= MAX_CHATLINE_HISTORY) {
159 void *history_data;
161 history_data = genlist_get(history_list, -1);
162 genlist_remove(history_list, history_data);
163 free(history_data);
166 genlist_prepend(history_list, fc_strdup(theinput));
167 history_pos=-1;
170 gtk_entry_set_text(w, "");
173 /**************************************************************************
174 Returns the name of player or user, set in the same list.
175 **************************************************************************/
176 static const char *get_player_or_user_name(int id)
178 size_t size = conn_list_size(game.all_connections);
180 if (id < size) {
181 return conn_list_get(game.all_connections, id)->username;
182 } else {
183 struct player *pplayer = player_by_number(id - size);
184 if (pplayer) {
185 return pplayer->name;
186 } else {
187 /* Empty slot. Relies on being used with comparison function
188 * which can cope with NULL. */
189 return NULL;
194 /**************************************************************************
195 Find a player or a user by prefix.
197 prefix - The prefix.
198 matches - A string array to set the matches result.
199 max_matches - The maximum of matches.
200 match_len - The length of the string used to returns matches.
202 Returns the number of the matches names.
203 **************************************************************************/
204 static int check_player_or_user_name(const char *prefix,
205 const char **matches,
206 const int max_matches)
208 int matches_id[max_matches * 2], ind, num;
210 switch (match_prefix_full(get_player_or_user_name,
211 player_slot_count()
212 + conn_list_size(game.all_connections),
213 MAX_LEN_NAME, fc_strncasecmp, strlen,
214 prefix, &ind, matches_id,
215 max_matches * 2, &num)) {
216 case M_PRE_EXACT:
217 case M_PRE_ONLY:
218 matches[0] = get_player_or_user_name(ind);
219 return 1;
220 case M_PRE_AMBIGUOUS:
222 /* Remove duplications playername/username. */
223 const char *name;
224 int i, j, c = 0;
226 for (i = 0; i < num && c < max_matches; i++) {
227 name = get_player_or_user_name(matches_id[i]);
228 for (j = 0; j < c; j++) {
229 if (0 == fc_strncasecmp(name, matches[j], MAX_LEN_NAME)) {
230 break;
233 if (j >= c) {
234 matches[c++] = name;
237 return c;
239 case M_PRE_EMPTY:
240 case M_PRE_LONG:
241 case M_PRE_FAIL:
242 case M_PRE_LAST:
243 break;
246 return 0;
249 /**************************************************************************
250 Find the larger common prefix.
252 prefixes - A list of prefixes.
253 num_prefixes - The number of prefixes.
254 buf - The buffer to set.
255 buf_len - The maximal size of the buffer.
257 Returns the length of the common prefix (in characters).
258 **************************************************************************/
259 static size_t get_common_prefix(const char *const *prefixes,
260 size_t num_prefixes,
261 char *buf, size_t buf_len)
263 const char *p;
264 char *q;
265 size_t i;
267 fc_strlcpy(buf, prefixes[0], buf_len);
268 for (i = 1; i < num_prefixes; i++) {
269 for (p = prefixes[i], q = buf; *p != '\0' && *q != '\0';
270 p = g_utf8_next_char(p), q = g_utf8_next_char(q)) {
271 if (g_unichar_toupper(g_utf8_get_char(p))
272 != g_unichar_toupper(g_utf8_get_char(q))) {
273 *q = '\0';
274 break;
279 return g_utf8_strlen(buf, -1);
282 /**************************************************************************
283 Autocompletes the input line with a player or user name.
284 Returns FALSE if there is no string to complete.
285 **************************************************************************/
286 static bool chatline_autocomplete(GtkEditable *editable)
288 #define MAX_MATCHES 10
289 const char *name[MAX_MATCHES];
290 char buf[MAX_LEN_NAME * MAX_MATCHES];
291 gint pos;
292 gchar *chars, *p, *prev;
293 int num, i;
294 size_t prefix_len;
296 /* Part 1: get the string to complete. */
297 pos = gtk_editable_get_position(editable);
298 chars = gtk_editable_get_chars(editable, 0, pos);
300 p = chars + strlen(chars);
301 while ((prev = g_utf8_find_prev_char(chars, p))) {
302 if (!g_unichar_isalnum(g_utf8_get_char(prev))) {
303 break;
305 p = prev;
307 /* p points to the start of the last word, or the start of the string. */
309 prefix_len = g_utf8_strlen(p, -1);
310 if (0 == prefix_len) {
311 /* Empty: nothing to complete, propagate the event. */
312 g_free(chars);
313 return FALSE;
316 /* Part 2: compare with player and user names. */
317 num = check_player_or_user_name(p, name, MAX_MATCHES);
318 if (1 == num) {
319 gtk_editable_delete_text(editable, pos - prefix_len, pos);
320 pos -= prefix_len;
321 gtk_editable_insert_text(editable, name[0], strlen(name[0]), &pos);
322 gtk_editable_set_position(editable, pos);
323 g_free(chars);
324 return TRUE;
325 } else if (num > 1) {
326 if (get_common_prefix(name, num, buf, sizeof(buf)) > prefix_len) {
327 gtk_editable_delete_text(editable, pos - prefix_len, pos);
328 pos -= prefix_len;
329 gtk_editable_insert_text(editable, buf, strlen(buf), &pos);
330 gtk_editable_set_position(editable, pos);
332 sz_strlcpy(buf, name[0]);
333 for (i = 1; i < num; i++) {
334 cat_snprintf(buf, sizeof(buf), ", %s", name[i]);
336 /* TRANS: comma-separated list of player/user names for completion */
337 output_window_printf(ftc_client, _("Suggestions: %s."), buf);
340 g_free(chars);
341 return TRUE;
344 /**************************************************************************
345 Called when a key is pressed.
346 **************************************************************************/
347 static gboolean inputline_handler(GtkWidget *w, GdkEventKey *ev)
349 if ((ev->state & GDK_CONTROL_MASK)) {
350 /* Chatline featured text support. */
351 switch (ev->keyval) {
352 case GDK_b:
353 inputline_make_tag(GTK_ENTRY(w), TTT_BOLD);
354 return TRUE;
356 case GDK_c:
357 inputline_make_tag(GTK_ENTRY(w), TTT_COLOR);
358 return TRUE;
360 case GDK_i:
361 inputline_make_tag(GTK_ENTRY(w), TTT_ITALIC);
362 return TRUE;
364 case GDK_s:
365 inputline_make_tag(GTK_ENTRY(w), TTT_STRIKE);
366 return TRUE;
368 case GDK_u:
369 inputline_make_tag(GTK_ENTRY(w), TTT_UNDERLINE);
370 return TRUE;
372 default:
373 break;
376 } else {
377 /* Chatline history controls. */
378 switch (ev->keyval) {
379 case GDK_Up:
380 if (history_pos < genlist_size(history_list) - 1) {
381 gtk_entry_set_text(GTK_ENTRY(w),
382 genlist_get(history_list, ++history_pos));
383 gtk_editable_set_position(GTK_EDITABLE(w), -1);
385 return TRUE;
387 case GDK_Down:
388 if (history_pos >= 0) {
389 history_pos--;
392 if (history_pos >= 0) {
393 gtk_entry_set_text(GTK_ENTRY(w),
394 genlist_get(history_list, history_pos));
395 } else {
396 gtk_entry_set_text(GTK_ENTRY(w), "");
398 gtk_editable_set_position(GTK_EDITABLE(w), -1);
399 return TRUE;
401 case GDK_Tab:
402 if (gui_options.gui_gtk2_chatline_autocompletion) {
403 return chatline_autocomplete(GTK_EDITABLE(w));
406 default:
407 break;
411 return FALSE;
414 /**************************************************************************
415 Make a text tag for the selected text.
416 **************************************************************************/
417 void inputline_make_tag(GtkEntry *entry, enum text_tag_type type)
419 char buf[MAX_LEN_MSG];
420 GtkEditable *editable = GTK_EDITABLE(entry);
421 gint start_pos, end_pos;
422 gchar *selection;
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);
429 selection = gtk_editable_get_chars(editable, start_pos, end_pos);
431 if (type == TTT_COLOR) {
432 /* Get the color arguments. */
433 char fg_color_text[32], bg_color_text[32];
434 GdkColor *fg_color = g_object_get_data(G_OBJECT(entry), "fg_color");
435 GdkColor *bg_color = g_object_get_data(G_OBJECT(entry), "bg_color");
437 if (!fg_color && !bg_color) {
438 goto CLEAN_UP;
441 color_to_string(fg_color, fg_color_text, sizeof(fg_color_text));
442 color_to_string(bg_color, bg_color_text, sizeof(bg_color_text));
444 if (0 == featured_text_apply_tag(selection, buf, sizeof(buf),
445 TTT_COLOR, 0, FT_OFFSET_UNSET,
446 ft_color_construct(fg_color_text,
447 bg_color_text))) {
448 goto CLEAN_UP;
450 } else if (0 == featured_text_apply_tag(selection, buf, sizeof(buf),
451 type, 0, FT_OFFSET_UNSET)) {
452 goto CLEAN_UP;
455 /* Replace the selection. */
456 gtk_editable_delete_text(editable, start_pos, end_pos);
457 end_pos = start_pos;
458 gtk_editable_insert_text(editable, buf, -1, &end_pos);
459 gtk_editable_select_region(editable, start_pos, end_pos);
461 CLEAN_UP:
462 g_free(selection);
465 /**************************************************************************
466 Make a chat link at the current position or make the current selection
467 clickable.
468 **************************************************************************/
469 void inputline_make_chat_link(struct tile *ptile, bool unit)
471 char buf[MAX_LEN_MSG];
472 GtkWidget *entry = toolkit.entry;
473 GtkEditable *editable = GTK_EDITABLE(entry);
474 gint start_pos, end_pos;
475 gchar *chars;
476 struct unit *punit;
478 /* Get the target. */
479 if (unit) {
480 punit = find_visible_unit(ptile);
481 if (!punit) {
482 output_window_append(ftc_client, _("No visible unit on this tile."));
483 return;
485 } else {
486 punit = NULL;
489 if (gtk_editable_get_selection_bounds(editable, &start_pos, &end_pos)) {
490 /* There is a selection, make it clickable. */
491 gpointer target;
492 enum text_link_type type;
494 chars = gtk_editable_get_chars(editable, start_pos, end_pos);
495 if (punit) {
496 type = TLT_UNIT;
497 target = punit;
498 } else if (tile_city(ptile)) {
499 type = TLT_CITY;
500 target = tile_city(ptile);
501 } else {
502 type = TLT_TILE;
503 target = ptile;
506 if (0 != featured_text_apply_tag(chars, buf, sizeof(buf), TTT_LINK,
507 0, FT_OFFSET_UNSET, type, target)) {
508 /* Replace the selection. */
509 gtk_editable_delete_text(editable, start_pos, end_pos);
510 end_pos = start_pos;
511 gtk_editable_insert_text(editable, buf, -1, &end_pos);
512 gtk_widget_grab_focus(entry);
513 gtk_editable_select_region(editable, start_pos, end_pos);
515 } else {
516 /* Just insert the link at the current position. */
517 start_pos = gtk_editable_get_position(editable);
518 end_pos = start_pos;
519 chars = gtk_editable_get_chars(editable, MAX(start_pos - 1, 0),
520 start_pos + 1);
521 if (punit) {
522 sz_strlcpy(buf, unit_link(punit));
523 } else if (tile_city(ptile)) {
524 sz_strlcpy(buf, city_link(tile_city(ptile)));
525 } else {
526 sz_strlcpy(buf, tile_link(ptile));
529 if (start_pos > 0 && strlen(chars) > 0 && chars[0] != ' ') {
530 /* Maybe insert an extra space. */
531 gtk_editable_insert_text(editable, " ", 1, &end_pos);
533 gtk_editable_insert_text(editable, buf, -1, &end_pos);
534 if (chars[start_pos > 0 ? 1 : 0] != '\0'
535 && chars[start_pos > 0 ? 1 : 0] != ' ') {
536 /* Maybe insert an extra space. */
537 gtk_editable_insert_text(editable, " ", 1, &end_pos);
539 gtk_widget_grab_focus(entry);
540 gtk_editable_set_position(editable, end_pos);
543 g_free(chars);
546 /**************************************************************************
547 Scroll a textview so that the given mark is visible, but only if the
548 scroll window containing the textview is very close to the bottom. The
549 text mark 'scroll_target' should probably be the first character of the
550 last line in the text buffer.
551 **************************************************************************/
552 void scroll_if_necessary(GtkTextView *textview, GtkTextMark *scroll_target)
554 GtkWidget *sw;
555 GtkAdjustment *vadj;
556 gdouble val, max, upper, page_size;
558 fc_assert_ret(textview != NULL);
559 fc_assert_ret(scroll_target != NULL);
561 sw = gtk_widget_get_parent(GTK_WIDGET(textview));
562 fc_assert_ret(sw != NULL);
563 fc_assert_ret(GTK_IS_SCROLLED_WINDOW(sw));
565 vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
566 val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
567 g_object_get(G_OBJECT(vadj), "upper", &upper,
568 "page-size", &page_size, NULL);
569 max = upper - page_size;
570 if (max - val < 10.0) {
571 gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), scroll_target,
572 0.0, TRUE, 1.0, 0.0);
576 /**************************************************************************
577 Click a link.
578 **************************************************************************/
579 static gboolean event_after(GtkWidget *text_view, GdkEventButton *event)
581 GtkTextIter start, end, iter;
582 GtkTextBuffer *buffer;
583 GSList *tags, *tagp;
584 gint x, y;
585 struct tile *ptile = NULL;
587 if (event->type != GDK_BUTTON_RELEASE || event->button != 1) {
588 return FALSE;
591 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
593 /* We shouldn't follow a link if the user has selected something. */
594 gtk_text_buffer_get_selection_bounds(buffer, &start, &end);
595 if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) {
596 return FALSE;
599 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW (text_view),
600 GTK_TEXT_WINDOW_WIDGET,
601 event->x, event->y, &x, &y);
603 gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(text_view), &iter, x, y);
605 if ((tags = gtk_text_iter_get_tags(&iter))) {
606 for (tagp = tags; tagp; tagp = tagp->next) {
607 GtkTextTag *tag = tagp->data;
608 enum text_link_type type =
609 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "type"));
611 if (type != 0) {
612 /* This is a link. */
613 int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tag), "id"));
614 ptile = NULL;
616 /* Real type is type - 1.
617 * See comment in apply_text_tag() for g_object_set_data(). */
618 type--;
620 switch (type) {
621 case TLT_CITY:
623 struct city *pcity = game_city_by_number(id);
625 if (pcity) {
626 ptile = client_city_tile(pcity);
627 } else {
628 output_window_append(ftc_client, _("This city isn't known!"));
631 break;
632 case TLT_TILE:
633 ptile = index_to_tile(id);
635 if (!ptile) {
636 output_window_append(ftc_client,
637 _("This tile doesn't exist in this game!"));
639 break;
640 case TLT_UNIT:
642 struct unit *punit = game_unit_by_number(id);
644 if (punit) {
645 ptile = unit_tile(punit);
646 } else {
647 output_window_append(ftc_client, _("This unit isn't known!"));
650 break;
653 if (ptile) {
654 center_tile_mapcanvas(ptile);
655 link_mark_restore(type, id);
656 gtk_widget_grab_focus(GTK_WIDGET(map_canvas));
660 g_slist_free(tags);
663 return FALSE;
666 /**************************************************************************
667 Set the "hand" cursor when moving over a link.
668 **************************************************************************/
669 static void set_cursor_if_appropriate(GtkTextView *text_view, gint x, gint y)
671 static gboolean hovering_over_link = FALSE;
672 static GdkCursor *hand_cursor = NULL;
673 static GdkCursor *regular_cursor = NULL;
674 GSList *tags, *tagp;
675 GtkTextIter iter;
676 gboolean hovering = FALSE;
678 /* Initialize the cursors. */
679 if (!hand_cursor) {
680 hand_cursor = gdk_cursor_new_for_display(
681 gdk_screen_get_display(gdk_screen_get_default()), GDK_HAND2);
683 if (!regular_cursor) {
684 regular_cursor = gdk_cursor_new_for_display(
685 gdk_screen_get_display(gdk_screen_get_default()), GDK_XTERM);
688 gtk_text_view_get_iter_at_location(text_view, &iter, x, y);
690 tags = gtk_text_iter_get_tags(&iter);
691 for (tagp = tags; tagp; tagp = tagp->next) {
692 enum text_link_type type =
693 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(tagp->data), "type"));
695 if (type != 0) {
696 hovering = TRUE;
697 break;
701 if (hovering != hovering_over_link) {
702 hovering_over_link = hovering;
704 if (hovering_over_link) {
705 gdk_window_set_cursor(gtk_text_view_get_window(text_view,
706 GTK_TEXT_WINDOW_TEXT),
707 hand_cursor);
708 } else {
709 gdk_window_set_cursor(gtk_text_view_get_window(text_view,
710 GTK_TEXT_WINDOW_TEXT),
711 regular_cursor);
715 if (tags) {
716 g_slist_free(tags);
720 /**************************************************************************
721 Maybe are the mouse is moving over a link.
722 **************************************************************************/
723 static gboolean motion_notify_event(GtkWidget *text_view,
724 GdkEventMotion *event)
726 gint x, y;
728 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view),
729 GTK_TEXT_WINDOW_WIDGET,
730 event->x, event->y, &x, &y);
731 set_cursor_if_appropriate(GTK_TEXT_VIEW(text_view), x, y);
732 gdk_window_get_pointer(text_view->window, NULL, NULL, NULL);
734 return FALSE;
737 /**************************************************************************
738 Maybe are the mouse is moving over a link.
739 **************************************************************************/
740 static gboolean visibility_notify_event(GtkWidget *text_view,
741 GdkEventVisibility *event)
743 gint wx, wy, bx, by;
745 gdk_window_get_pointer(text_view->window, &wx, &wy, NULL);
746 gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW (text_view),
747 GTK_TEXT_WINDOW_WIDGET,
748 wx, wy, &bx, &by);
749 set_cursor_if_appropriate(GTK_TEXT_VIEW(text_view), bx, by);
751 return FALSE;
754 /**************************************************************************
755 Set the appropriate callbacks for the message buffer.
756 **************************************************************************/
757 void set_message_buffer_view_link_handlers(GtkWidget *view)
759 g_signal_connect(view, "event-after",
760 G_CALLBACK(event_after), NULL);
761 g_signal_connect(view, "motion-notify-event",
762 G_CALLBACK(motion_notify_event), NULL);
763 g_signal_connect(view, "visibility-notify-event",
764 G_CALLBACK(visibility_notify_event), NULL);
768 /**************************************************************************
769 Convert a struct text_tag to a GtkTextTag.
770 **************************************************************************/
771 void apply_text_tag(const struct text_tag *ptag, GtkTextBuffer *buf,
772 ft_offset_t text_start_offset, const char *text)
774 static bool initalized = FALSE;
775 GtkTextIter start, stop;
777 if (!initalized) {
778 gtk_text_buffer_create_tag(buf, "bold",
779 "weight", PANGO_WEIGHT_BOLD, NULL);
780 gtk_text_buffer_create_tag(buf, "italic",
781 "style", PANGO_STYLE_ITALIC, NULL);
782 gtk_text_buffer_create_tag(buf, "strike",
783 "strikethrough", TRUE, NULL);
784 gtk_text_buffer_create_tag(buf, "underline",
785 "underline", PANGO_UNDERLINE_SINGLE, NULL);
786 initalized = TRUE;
789 /* Get the position. */
791 * N.B.: text_tag_*_offset() value is in bytes, so we need to convert it
792 * to utf8 character offset.
794 gtk_text_buffer_get_iter_at_offset(buf, &start, text_start_offset
795 + g_utf8_pointer_to_offset(text,
796 text + text_tag_start_offset(ptag)));
797 if (text_tag_stop_offset(ptag) == FT_OFFSET_UNSET) {
798 gtk_text_buffer_get_end_iter(buf, &stop);
799 } else {
800 gtk_text_buffer_get_iter_at_offset(buf, &stop, text_start_offset
801 + g_utf8_pointer_to_offset(text,
802 text + text_tag_stop_offset(ptag)));
805 switch (text_tag_type(ptag)) {
806 case TTT_BOLD:
807 gtk_text_buffer_apply_tag_by_name(buf, "bold", &start, &stop);
808 break;
809 case TTT_ITALIC:
810 gtk_text_buffer_apply_tag_by_name(buf, "italic", &start, &stop);
811 break;
812 case TTT_STRIKE:
813 gtk_text_buffer_apply_tag_by_name(buf, "strike", &start, &stop);
814 break;
815 case TTT_UNDERLINE:
816 gtk_text_buffer_apply_tag_by_name(buf, "underline", &start, &stop);
817 break;
818 case TTT_COLOR:
820 /* We have to make a new tag every time. */
821 GtkTextTag *tag = NULL;
822 GdkColor foreground;
823 GdkColor background;
825 if (gdk_color_parse(text_tag_color_foreground(ptag), &foreground)) {
826 if (gdk_color_parse(text_tag_color_background(ptag),
827 &background)) {
828 tag = gtk_text_buffer_create_tag(buf, NULL,
829 "foreground-gdk", &foreground,
830 "background-gdk", &background,
831 NULL);
832 } else {
833 tag = gtk_text_buffer_create_tag(buf, NULL,
834 "foreground-gdk", &foreground,
835 NULL);
837 } else if (gdk_color_parse(text_tag_color_background(ptag),
838 &background)) {
839 tag = gtk_text_buffer_create_tag(buf, NULL,
840 "background-gdk", &background,
841 NULL);
844 if (!tag) {
845 break; /* No color. */
847 gtk_text_buffer_apply_tag(buf, tag, &start, &stop);
848 g_object_unref(G_OBJECT(tag));
850 break;
851 case TTT_LINK:
853 struct color *pcolor = NULL;
854 GtkTextTag *tag;
856 switch (text_tag_link_type(ptag)) {
857 case TLT_CITY:
858 pcolor = get_color(tileset, COLOR_MAPVIEW_CITY_LINK);
859 break;
860 case TLT_TILE:
861 pcolor = get_color(tileset, COLOR_MAPVIEW_TILE_LINK);
862 break;
863 case TLT_UNIT:
864 pcolor = get_color(tileset, COLOR_MAPVIEW_UNIT_LINK);
865 break;
868 if (!pcolor) {
869 break; /* Not a valid link type case. */
872 tag = gtk_text_buffer_create_tag(buf, NULL,
873 "foreground-gdk", &pcolor->color,
874 "underline", PANGO_UNDERLINE_SINGLE,
875 NULL);
877 /* Type 0 is reserved for non-link tags. So, add 1 to the
878 * type value. */
879 g_object_set_data(G_OBJECT(tag), "type",
880 GINT_TO_POINTER(text_tag_link_type(ptag) + 1));
881 g_object_set_data(G_OBJECT(tag), "id",
882 GINT_TO_POINTER(text_tag_link_id(ptag)));
883 gtk_text_buffer_apply_tag(buf, tag, &start, &stop);
884 g_object_unref(G_OBJECT(tag));
885 break;
890 /**************************************************************************
891 Appends the string to the chat output window. The string should be
892 inserted on its own line, although it will have no newline.
893 **************************************************************************/
894 void real_output_window_append(const char *astring,
895 const struct text_tag_list *tags,
896 int conn_id)
898 GtkTextBuffer *buf;
899 GtkTextIter iter;
900 GtkTextMark *mark;
901 ft_offset_t text_start_offset;
903 buf = message_buffer;
905 if (buf == NULL) {
906 log_error("Output when no message buffer: %s", astring);
908 return;
911 gtk_text_buffer_get_end_iter(buf, &iter);
912 gtk_text_buffer_insert(buf, &iter, "\n", -1);
913 mark = gtk_text_buffer_create_mark(buf, NULL, &iter, TRUE);
915 if (gui_options.gui_gtk2_show_chat_message_time) {
916 char timebuf[64];
917 time_t now;
918 struct tm *now_tm;
920 now = time(NULL);
921 now_tm = localtime(&now);
922 strftime(timebuf, sizeof(timebuf), "[%H:%M:%S] ", now_tm);
923 gtk_text_buffer_insert(buf, &iter, timebuf, -1);
926 text_start_offset = gtk_text_iter_get_offset(&iter);
927 gtk_text_buffer_insert(buf, &iter, astring, -1);
928 text_tag_list_iterate(tags, ptag) {
929 apply_text_tag(ptag, buf, text_start_offset, astring);
930 } text_tag_list_iterate_end;
932 if (main_message_area) {
933 scroll_if_necessary(GTK_TEXT_VIEW(main_message_area), mark);
935 if (start_message_area) {
936 scroll_if_necessary(GTK_TEXT_VIEW(start_message_area), mark);
938 gtk_text_buffer_delete_mark(buf, mark);
940 append_network_statusbar(astring, FALSE);
943 /**************************************************************************
944 I have no idea what module this belongs in -- Syela
945 I've decided to put output_window routines in chatline.c, because
946 the are somewhat related and output_window_* is already here. --dwp
947 **************************************************************************/
948 void log_output_window(void)
950 GtkTextIter start, end;
951 gchar *txt;
953 gtk_text_buffer_get_bounds(message_buffer, &start, &end);
954 txt = gtk_text_buffer_get_text(message_buffer, &start, &end, TRUE);
956 write_chatline_content(txt);
957 g_free(txt);
960 /**************************************************************************
961 Clear output window. This does *not* destroy it, or free its resources
962 **************************************************************************/
963 void clear_output_window(void)
965 set_output_window_text(_("Cleared output window."));
968 /**************************************************************************
969 Set given text to output window
970 **************************************************************************/
971 void set_output_window_text(const char *text)
973 gtk_text_buffer_set_text(message_buffer, text, -1);
976 /**************************************************************************
977 Returns whether the chatline is scrolled to the bottom.
978 **************************************************************************/
979 bool chatline_is_scrolled_to_bottom(void)
981 GtkWidget *sw, *w;
982 GtkAdjustment *vadj;
983 gdouble val, max, upper, page_size;
985 if (get_client_page() == PAGE_GAME) {
986 w = GTK_WIDGET(main_message_area);
987 } else {
988 w = GTK_WIDGET(start_message_area);
991 if (w == NULL) {
992 return TRUE;
995 sw = gtk_widget_get_parent(w);
996 vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
997 val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
998 g_object_get(G_OBJECT(vadj), "upper", &upper,
999 "page-size", &page_size, NULL);
1000 max = upper - page_size;
1002 /* Approximation. */
1003 return max - val < 0.00000001;
1006 /**************************************************************************
1007 Scrolls the pregame and in-game chat windows all the way to the bottom.
1009 Why do we do it in such a convuluted fasion rather than calling
1010 chatline_scroll_to_bottom directly from toplevel_configure?
1011 Because the widget is not at its final size yet when the configure
1012 event occurs.
1013 **************************************************************************/
1014 static gboolean chatline_scroll_callback(gpointer data)
1016 chatline_scroll_to_bottom(FALSE); /* Not delayed this time! */
1018 *((guint *) data) = 0;
1019 return FALSE; /* Remove this idle function. */
1022 /**************************************************************************
1023 Scrolls the pregame and in-game chat windows all the way to the bottom.
1024 If delayed is TRUE, it will be done in a idle_callback.
1025 **************************************************************************/
1026 void chatline_scroll_to_bottom(bool delayed)
1028 static guint callback_id = 0;
1030 if (delayed) {
1031 if (callback_id == 0) {
1032 callback_id = g_idle_add(chatline_scroll_callback, &callback_id);
1034 } else if (message_buffer) {
1035 GtkTextIter end;
1037 gtk_text_buffer_get_end_iter(message_buffer, &end);
1039 if (main_message_area) {
1040 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_message_area),
1041 &end, 0.0, TRUE, 1.0, 0.0);
1043 if (start_message_area) {
1044 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(start_message_area),
1045 &end, 0.0, TRUE, 1.0, 0.0);
1050 /**************************************************************************
1051 Tool button clicked.
1052 **************************************************************************/
1053 static void make_tag_callback(GtkToolButton *button, gpointer data)
1055 inputline_make_tag(GTK_ENTRY(data),
1056 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button),
1057 "text_tag_type")));
1060 /**************************************************************************
1061 Set the color for an object. Update the button if not NULL.
1062 **************************************************************************/
1063 static void color_set(GObject *object, const gchar *color_target,
1064 GdkColor *color, GtkToolButton *button)
1066 GdkColor *current_color = g_object_get_data(object, color_target);
1067 GdkColormap *colormap = gdk_colormap_get_system();
1069 if (NULL == color) {
1070 /* Clears the current color. */
1071 if (NULL != current_color) {
1072 gdk_colormap_free_colors(colormap, current_color, 1);
1073 gdk_color_free(current_color);
1074 g_object_set_data(object, color_target, NULL);
1075 if (NULL != button) {
1076 gtk_tool_button_set_icon_widget(button, NULL);
1079 } else {
1080 /* Apply the new color. */
1081 if (NULL != current_color) {
1082 /* We already have a GdkColor pointer. */
1083 gdk_colormap_free_colors(colormap, current_color, 1);
1084 *current_color = *color;
1085 } else {
1086 /* We need to make a GdkColor pointer. */
1087 current_color = gdk_color_copy(color);
1088 g_object_set_data(object, color_target, current_color);
1091 gdk_colormap_alloc_color(colormap, current_color, TRUE, TRUE);
1092 if (NULL != button) {
1093 /* Update the button. */
1094 GdkPixmap *pixmap;
1095 GtkWidget *image;
1097 pixmap = gdk_pixmap_new(root_window, 16, 16, -1);
1098 gdk_gc_set_foreground(fill_bg_gc, current_color);
1099 gdk_draw_rectangle(pixmap, fill_bg_gc, TRUE, 0, 0, 16, 16);
1100 image = gtk_image_new_from_pixmap(pixmap, NULL);
1101 gtk_tool_button_set_icon_widget(button, image);
1102 gtk_widget_show(image);
1103 g_object_unref(G_OBJECT(pixmap));
1108 /**************************************************************************
1109 Color selection dialog response.
1110 **************************************************************************/
1111 static void color_selected(GtkDialog *dialog, gint res, gpointer data)
1113 GtkToolButton *button =
1114 GTK_TOOL_BUTTON(g_object_get_data(G_OBJECT(dialog), "button"));
1115 const gchar *color_target =
1116 g_object_get_data(G_OBJECT(button), "color_target");
1118 if (res == GTK_RESPONSE_REJECT) {
1119 /* Clears the current color. */
1120 color_set(G_OBJECT(data), color_target, NULL, button);
1121 } else if (res == GTK_RESPONSE_OK) {
1122 /* Apply the new color. */
1123 GtkColorSelection *selection =
1124 GTK_COLOR_SELECTION(g_object_get_data(G_OBJECT(dialog), "selection"));
1125 GdkColor new_color;
1127 gtk_color_selection_get_current_color(selection, &new_color);
1128 color_set(G_OBJECT(data), color_target, &new_color, button);
1131 gtk_widget_destroy(GTK_WIDGET(dialog));
1134 /**************************************************************************
1135 Color selection tool button clicked.
1136 **************************************************************************/
1137 static void select_color_callback(GtkToolButton *button, gpointer data)
1139 GtkWidget *dialog, *selection;
1140 /* "fg_color" or "bg_color". */
1141 const gchar *color_target = g_object_get_data(G_OBJECT(button),
1142 "color_target");
1143 GdkColor *current_color = g_object_get_data(G_OBJECT(data), color_target);
1145 /* TRANS: "text" or "background". */
1146 gchar *buf = g_strdup_printf(_("Select the %s color"),
1147 (const char *) g_object_get_data(G_OBJECT(button),
1148 "color_info"));
1149 dialog = gtk_dialog_new_with_buttons(buf, NULL, GTK_DIALOG_MODAL,
1150 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1151 GTK_STOCK_CLEAR, GTK_RESPONSE_REJECT,
1152 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1153 setup_dialog(dialog, toplevel);
1154 g_object_set_data(G_OBJECT(dialog), "button", button);
1155 g_signal_connect(dialog, "response", G_CALLBACK(color_selected), data);
1157 selection = gtk_color_selection_new();
1158 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), selection,
1159 FALSE, FALSE, 0);
1160 g_object_set_data(G_OBJECT(dialog), "selection", selection);
1161 if (current_color) {
1162 gtk_color_selection_set_current_color(GTK_COLOR_SELECTION(selection),
1163 current_color);
1166 gtk_widget_show_all(dialog);
1167 g_free(buf);
1170 /**************************************************************************
1171 Moves the tool kit to the toolkit view.
1172 **************************************************************************/
1173 static gboolean move_toolkit(GtkWidget *toolkit_view, GdkEventExpose *event,
1174 gpointer data)
1176 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1177 GtkWidget *parent = gtk_widget_get_parent(ptoolkit->main_widget);
1178 GtkWidget *button_box = GTK_WIDGET(g_object_get_data(G_OBJECT(toolkit_view),
1179 "button_box"));
1180 GList *list, *iter;
1182 if (parent) {
1183 if (parent == toolkit_view) {
1184 return FALSE; /* Already owned. */
1187 /* N.B.: We need to hide/show the toolbar to reset the sensitivity
1188 * of the tool buttons. */
1189 if (ptoolkit->toolbar_displayed) {
1190 gtk_widget_hide(ptoolkit->toolbar);
1192 gtk_widget_reparent(ptoolkit->main_widget, toolkit_view);
1193 if (ptoolkit->toolbar_displayed) {
1194 gtk_widget_show(ptoolkit->toolbar);
1197 if (!gtk_widget_get_parent(button_box)) {
1198 /* Attach to the toolkit button_box. */
1199 gtk_box_pack_end(GTK_BOX(ptoolkit->button_box), button_box,
1200 FALSE, FALSE, 0);
1202 gtk_widget_show_all(ptoolkit->main_widget);
1204 /* Hide all other buttons boxes. */
1205 list = gtk_container_get_children(GTK_CONTAINER(ptoolkit->button_box));
1206 for (iter = list; iter != NULL; iter = g_list_next(iter)) {
1207 GtkWidget *widget = GTK_WIDGET(iter->data);
1209 if (widget != button_box) {
1210 gtk_widget_hide(widget);
1213 g_list_free(list);
1215 } else {
1216 /* First time attached to a parent. */
1217 gtk_box_pack_start(GTK_BOX(toolkit_view), ptoolkit->main_widget,
1218 TRUE, TRUE, 0);
1219 gtk_box_pack_end(GTK_BOX(ptoolkit->button_box), button_box,
1220 FALSE, FALSE, 0);
1221 gtk_widget_show_all(ptoolkit->main_widget);
1224 return FALSE;
1227 /**************************************************************************
1228 Show/Hide the toolbar.
1229 **************************************************************************/
1230 static gboolean set_toolbar_visibility(GtkWidget *w,
1231 GdkEventExpose *event,
1232 gpointer data)
1234 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1235 GtkToggleButton *button = GTK_TOGGLE_BUTTON(toolkit.toggle_button);
1237 if (ptoolkit->toolbar_displayed) {
1238 if (!gtk_toggle_button_get_active(button)) {
1239 /* button_toggled() will be called and the toolbar shown. */
1240 gtk_toggle_button_set_active(button, TRUE);
1241 } else {
1242 /* Unsure the widget is visible. */
1243 gtk_widget_show(ptoolkit->toolbar);
1245 } else {
1246 if (gtk_toggle_button_get_active(button)) {
1247 /* button_toggled() will be called and the toolbar hiden. */
1248 gtk_toggle_button_set_active(button, FALSE);
1249 } else {
1250 /* Unsure the widget is visible. */
1251 gtk_widget_hide(ptoolkit->toolbar);
1255 return FALSE;
1258 /**************************************************************************
1259 Show/Hide the toolbar.
1260 **************************************************************************/
1261 static void button_toggled(GtkToggleButton *button, gpointer data)
1263 struct inputline_toolkit *ptoolkit = (struct inputline_toolkit *) data;
1265 if (gtk_toggle_button_get_active(button)) {
1266 gtk_widget_show(ptoolkit->toolbar);
1267 ptoolkit->toolbar_displayed = TRUE;
1268 if (chatline_is_scrolled_to_bottom()) {
1269 /* Make sure to be still at the end. */
1270 chatline_scroll_to_bottom(TRUE);
1272 } else {
1273 gtk_widget_hide(ptoolkit->toolbar);
1274 ptoolkit->toolbar_displayed = FALSE;
1278 /**************************************************************************
1279 Returns a new inputline toolkit view widget that can contain the
1280 inputline.
1282 This widget has the following datas:
1283 "button_box": pointer to the GtkHBox where to append buttons.
1284 **************************************************************************/
1285 GtkWidget *inputline_toolkit_view_new(void)
1287 GtkWidget *toolkit_view, *bbox;
1289 /* Main widget. */
1290 toolkit_view = gtk_vbox_new(FALSE, 0);
1291 g_signal_connect(toolkit_view, "expose-event",
1292 G_CALLBACK(move_toolkit), &toolkit);
1294 /* Button box. */
1295 bbox = gtk_hbox_new(FALSE, 12);
1296 g_object_set_data(G_OBJECT(toolkit_view), "button_box", bbox);
1298 return toolkit_view;
1301 /**************************************************************************
1302 Appends a button to the inputline toolkit view widget.
1303 **************************************************************************/
1304 void inputline_toolkit_view_append_button(GtkWidget *toolkit_view,
1305 GtkWidget *button)
1307 gtk_box_pack_start(GTK_BOX(g_object_get_data(G_OBJECT(toolkit_view),
1308 "button_box")), button, FALSE, FALSE, 0);
1311 /**************************************************************************
1312 Initializes the chatline stuff.
1313 **************************************************************************/
1314 void chatline_init(void)
1316 GtkWidget *vbox, *toolbar, *hbox, *button, *entry, *bbox;
1317 GtkToolItem *item;
1318 GdkColor color;
1320 /* Chatline history. */
1321 if (!history_list) {
1322 history_list = genlist_new();
1323 history_pos = -1;
1326 /* Inputline toolkit. */
1327 memset(&toolkit, 0, sizeof(toolkit));
1329 vbox = gtk_vbox_new(FALSE, 2);
1330 toolkit.main_widget = vbox;
1331 g_signal_connect(vbox, "expose-event",
1332 G_CALLBACK(set_toolbar_visibility), &toolkit);
1334 entry = gtk_entry_new();
1335 toolkit.entry = entry;
1337 /* First line: toolbar */
1338 toolbar = gtk_toolbar_new();
1339 gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
1340 gtk_toolbar_set_icon_size(GTK_TOOLBAR(toolbar), GTK_ICON_SIZE_MENU);
1341 gtk_toolbar_set_show_arrow(GTK_TOOLBAR(toolbar), FALSE);
1342 gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_BOTH_HORIZ);
1343 gtk_toolbar_set_orientation(GTK_TOOLBAR(toolbar),
1344 GTK_ORIENTATION_HORIZONTAL);
1345 toolkit.toolbar = toolbar;
1347 /* Bold button. */
1348 item = gtk_tool_button_new_from_stock(GTK_STOCK_BOLD);
1349 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1350 g_object_set_data(G_OBJECT(item), "text_tag_type",
1351 GINT_TO_POINTER(TTT_BOLD));
1352 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1353 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Bold (Ctrl-B)"));
1355 /* Italic button. */
1356 item = gtk_tool_button_new_from_stock(GTK_STOCK_ITALIC);
1357 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1358 g_object_set_data(G_OBJECT(item), "text_tag_type",
1359 GINT_TO_POINTER(TTT_ITALIC));
1360 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1361 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Italic (Ctrl-I)"));
1363 /* Strike button. */
1364 item = gtk_tool_button_new_from_stock(GTK_STOCK_STRIKETHROUGH);
1365 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1366 g_object_set_data(G_OBJECT(item), "text_tag_type",
1367 GINT_TO_POINTER(TTT_STRIKE));
1368 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1369 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Strikethrough (Ctrl-S)"));
1371 /* Underline button. */
1372 item = gtk_tool_button_new_from_stock(GTK_STOCK_UNDERLINE);
1373 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1374 g_object_set_data(G_OBJECT(item), "text_tag_type",
1375 GINT_TO_POINTER(TTT_UNDERLINE));
1376 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1377 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Underline (Ctrl-U)"));
1379 /* Color button. */
1380 item = gtk_tool_button_new_from_stock(GTK_STOCK_SELECT_COLOR);
1381 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1382 g_object_set_data(G_OBJECT(item), "text_tag_type",
1383 GINT_TO_POINTER(TTT_COLOR));
1384 g_signal_connect(item, "clicked", G_CALLBACK(make_tag_callback), entry);
1385 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Color (Ctrl-C)"));
1387 gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
1388 gtk_separator_tool_item_new(), -1);
1390 /* Foreground selector. */
1391 item = gtk_tool_button_new(NULL, "");
1392 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1393 g_object_set_data(G_OBJECT(item), "color_target", fc_strdup("fg_color"));
1394 g_object_set_data(G_OBJECT(item), "color_info",
1395 fc_strdup(_("foreground")));
1396 g_signal_connect(item, "clicked",
1397 G_CALLBACK(select_color_callback), entry);
1398 gtk_widget_set_tooltip_text(GTK_WIDGET(item), _("Select the text color"));
1399 if (gdk_color_parse("#000000", &color)) {
1400 /* Set default foreground color. */
1401 color_set(G_OBJECT(entry), "fg_color", &color, GTK_TOOL_BUTTON(item));
1402 } else {
1403 log_error("Failed to set the default foreground color.");
1406 /* Background selector. */
1407 item = gtk_tool_button_new(NULL, "");
1408 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1409 g_object_set_data(G_OBJECT(item), "color_target", fc_strdup("bg_color"));
1410 g_object_set_data(G_OBJECT(item), "color_info",
1411 fc_strdup(_("background")));
1412 g_signal_connect(item, "clicked",
1413 G_CALLBACK(select_color_callback), entry);
1414 gtk_widget_set_tooltip_text(GTK_WIDGET(item),
1415 _("Select the background color"));
1416 if (gdk_color_parse("#ffffff", &color)) {
1417 /* Set default background color. */
1418 color_set(G_OBJECT(entry), "bg_color", &color, GTK_TOOL_BUTTON(item));
1419 } else {
1420 log_error("Failed to set the default background color.");
1423 gtk_toolbar_insert(GTK_TOOLBAR(toolbar),
1424 gtk_separator_tool_item_new(), -1);
1426 /* Return button. */
1427 item = gtk_tool_button_new_from_stock(GTK_STOCK_OK);
1428 gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, -1);
1429 g_signal_connect_swapped(item, "clicked",
1430 G_CALLBACK(inputline_return), entry);
1431 gtk_widget_set_tooltip_text(GTK_WIDGET(item),
1432 /* TRANS: "Return" means the return key. */
1433 _("Send the chat (Return)"));
1435 /* Second line */
1436 hbox = gtk_hbox_new(FALSE, 4);
1437 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
1439 /* Toggle button. */
1440 button = gtk_toggle_button_new();
1441 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 2);
1442 gtk_container_add(GTK_CONTAINER(button),
1443 #ifdef GTK_STOCK_EDIT
1444 gtk_image_new_from_stock(GTK_STOCK_EDIT,
1445 #else
1446 gtk_image_new_from_stock(GTK_STOCK_PREFERENCES,
1447 #endif
1448 GTK_ICON_SIZE_MENU));
1449 g_signal_connect(button, "toggled", G_CALLBACK(button_toggled), &toolkit);
1450 gtk_widget_set_tooltip_text(GTK_WIDGET(button), _("Chat tools"));
1451 toolkit.toggle_button = button;
1453 /* Entry. */
1454 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 2);
1455 g_signal_connect(entry, "activate", G_CALLBACK(inputline_return), NULL);
1456 g_signal_connect(entry, "key_press_event",
1457 G_CALLBACK(inputline_handler), NULL);
1459 /* Button box. */
1460 bbox = gtk_hbox_new(FALSE, 0);
1461 gtk_box_pack_end(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
1462 toolkit.button_box = bbox;
1465 /**************************************************************************
1466 Main thread side callback to print version message
1467 **************************************************************************/
1468 static gboolean version_message_main_thread(gpointer user_data)
1470 char *vertext = (char *)user_data;
1472 output_window_append(ftc_client, vertext);
1474 FC_FREE(vertext);
1476 return FALSE;
1479 /**************************************************************************
1480 Got version message from metaserver thread.
1481 **************************************************************************/
1482 void version_message(const char *vertext)
1484 int len = strlen(vertext) + 1;
1485 char *persistent = fc_malloc(len);
1487 strncpy(persistent, vertext, len);
1489 gdk_threads_add_idle(version_message_main_thread, persistent);