<URL: http://bugs.freeciv.org/Ticket/Display.html?id=40721 >

Attached patch cleans up and improves automatic chatline
scrolling, so that the chatline is scrolled to the bottom
when switching to the pregame or game page, and when the
chatline textview widget is resized.


-----------------------------------------------------------------------
一番おもしろい物は一番下にある。
 client/gui-gtk-2.0/chatline.c |  100 ++++++++++++++++++++++++++---------------
 client/gui-gtk-2.0/chatline.h |    1 +
 client/gui-gtk-2.0/gui_main.c |   44 ++++++++++++++++++
 client/gui-gtk-2.0/pages.c    |    4 ++
 4 files changed, 112 insertions(+), 37 deletions(-)

diff --git a/client/gui-gtk-2.0/chatline.c b/client/gui-gtk-2.0/chatline.c
index 33028d6..e8356f0 100644
--- a/client/gui-gtk-2.0/chatline.c
+++ b/client/gui-gtk-2.0/chatline.c
@@ -67,23 +67,50 @@ void inputline_return(GtkEntry *w, gpointer data)
 }
 
 /**************************************************************************
+  Scroll a textview so that the given mark is visible, but only if the
+  scroll window containing the textview is very close to the bottom. The
+  text mark 'scroll_target' should probably be the first character of the
+  last line in the text buffer.
+**************************************************************************/
+static void scroll_if_necessary(GtkTextView *textview,
+                                GtkTextMark *scroll_target)
+{
+  GtkWidget *sw;
+  GtkAdjustment *vadj;
+  gdouble val, max, upper, page_size;
+
+  g_return_if_fail(textview != NULL);
+  g_return_if_fail(scroll_target != NULL);
+
+  sw = gtk_widget_get_parent(GTK_WIDGET(textview));
+  g_return_if_fail(sw != NULL);
+  g_return_if_fail(GTK_IS_SCROLLED_WINDOW(sw));
+
+  vadj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
+  val = gtk_adjustment_get_value(GTK_ADJUSTMENT(vadj));
+  g_object_get(G_OBJECT(vadj), "upper", &upper,
+               "page-size", &page_size, NULL);
+  max = upper - page_size;
+  if (max - val < 10.0) {
+    gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(textview), scroll_target,
+                                 0.0, TRUE, 1.0, 0.0);
+  }
+}
+
+/**************************************************************************
   Appends the string to the chat output window.  The string should be
   inserted on its own line, although it will have no newline.
 **************************************************************************/
 void real_append_output_window(const char *astring, int conn_id)
 {
-  GtkWidget *sw;
-  GtkAdjustment *slider;
-  bool scroll;
-
   GtkTextBuffer *buf;
-  GtkTextIter i;
+  GtkTextIter iter;
   GtkTextMark *mark;
 
-
   buf = message_buffer;
-  gtk_text_buffer_get_end_iter(buf, &i);
-  gtk_text_buffer_insert(buf, &i, "\n", -1);
+  gtk_text_buffer_get_end_iter(buf, &iter);
+  gtk_text_buffer_insert(buf, &iter, "\n", -1);
+  mark = gtk_text_buffer_create_mark(buf, NULL, &iter, TRUE);
 
   if (show_chat_message_time) {
     char timebuf[64];
@@ -93,42 +120,19 @@ void real_append_output_window(const char *astring, int conn_id)
     now = time(NULL);
     localtime_r(&now, &now_tm);
     strftime(timebuf, sizeof(timebuf), "[%H:%M:%S] ", &now_tm);
-    gtk_text_buffer_insert(buf, &i, timebuf, -1);
+    gtk_text_buffer_insert(buf, &iter, timebuf, -1);
   }
 
-  gtk_text_buffer_insert(buf, &i, astring, -1);
-
-  /* have to use a mark, or this won't work properly */
-  gtk_text_buffer_get_end_iter(buf, &i);
-  mark = gtk_text_buffer_create_mark(buf, NULL, &i, FALSE);
+  gtk_text_buffer_insert(buf, &iter, astring, -1);
 
-
-  sw = gtk_widget_get_parent(GTK_WIDGET(main_message_area));
-  slider = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
-
-  /* scroll forward only if slider is near the bottom */
-  scroll = ((slider->value + slider->page_size) >=
-      (slider->upper - slider->step_increment));
-  if (scroll) {
-    gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(main_message_area),
-	mark);
+  if (main_message_area) {
+    scroll_if_necessary(GTK_TEXT_VIEW(main_message_area), mark);
   }
-
-  sw = gtk_widget_get_parent(GTK_WIDGET(start_message_area));
-  slider = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw));
-
-  /* scroll forward only if slider is near the bottom */
-  scroll = ((slider->value + slider->page_size) >=
-      (slider->upper - slider->step_increment));
-  if (scroll) {
-    gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(start_message_area),
-	mark);
+  if (start_message_area) {
+    scroll_if_necessary(GTK_TEXT_VIEW(start_message_area), mark);
   }
-
-
   gtk_text_buffer_delete_mark(buf, mark);
 
-
   append_network_statusbar(astring, FALSE);
 }
 
@@ -164,3 +168,25 @@ void set_output_window_text(const char *text)
 {
   gtk_text_buffer_set_text(message_buffer, text, -1);
 }
+
+/**************************************************************************
+  Scrolls the pregame and in-game chat windows all the way to the bottom.
+**************************************************************************/
+void chatline_scroll_to_bottom(void)
+{
+  GtkTextIter end;
+
+  if (!message_buffer) {
+    return;
+  }
+  gtk_text_buffer_get_end_iter(message_buffer, &end);
+
+  if (main_message_area) {
+    gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_message_area),
+                                 &end, 0.0, TRUE, 1.0, 0.0);
+  }
+  if (start_message_area) {
+    gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(start_message_area),
+                                 &end, 0.0, TRUE, 1.0, 0.0);
+  }
+}
diff --git a/client/gui-gtk-2.0/chatline.h b/client/gui-gtk-2.0/chatline.h
index ca5b8f3..4e64684 100644
--- a/client/gui-gtk-2.0/chatline.h
+++ b/client/gui-gtk-2.0/chatline.h
@@ -24,5 +24,6 @@ extern int history_pos;
 
 void inputline_return(GtkEntry *w, gpointer data);
 void set_output_window_text(const char *text);
+void chatline_scroll_to_bottom(void);
 
 #endif  /* FC__CHATLINE_H */
diff --git a/client/gui-gtk-2.0/gui_main.c b/client/gui-gtk-2.0/gui_main.c
index 109a055..e33762e 100644
--- a/client/gui-gtk-2.0/gui_main.c
+++ b/client/gui-gtk-2.0/gui_main.c
@@ -412,6 +412,48 @@ static gboolean toplevel_handler(GtkWidget *w, GdkEventKey *ev, gpointer data)
   return FALSE;
 }
 
+
+/**************************************************************************
+  Idle callback for scrolling down the chatline.
+  NB: Only to be used by queue_chatline_scroll_to_bottom().
+**************************************************************************/
+static gboolean chatline_scroll_callback(gpointer data)
+{
+  guint *pid = data;
+  chatline_scroll_to_bottom();
+  *pid = 0;
+  return FALSE; /* Remove this idle function. */
+}
+
+/**************************************************************************
+  Adds an idle callback to scroll the chatline to the bottom.
+**************************************************************************/
+static void queue_chatline_scroll_to_bottom(void)
+{
+  static guint id = 0;
+  if (id == 0) {
+    id = g_idle_add(chatline_scroll_callback, &id);
+  }
+}
+
+/**************************************************************************
+  When the chatline text view is resized, scroll it to the bottom. This
+  prevents users from accidentally missing messages when the chatline
+  gets scrolled up a small amount and stops scrolling down automatically.
+**************************************************************************/
+static void main_message_area_size_allocate(GtkWidget *widget,
+                                            GtkAllocation *allocation,
+                                            gpointer data)
+{
+  static int old_width = 0, old_height = 0;
+  if (allocation->width != old_width
+      || allocation->height != old_height) {
+    queue_chatline_scroll_to_bottom();
+    old_width = allocation->width;
+    old_height = allocation->height;
+  }
+}
+
 /**************************************************************************
 ...
 **************************************************************************/
@@ -1226,6 +1268,8 @@ static void setup_widgets(void)
   text = gtk_text_view_new_with_buffer(message_buffer);
   gtk_text_view_set_editable(GTK_TEXT_VIEW(text), FALSE);
   gtk_container_add(GTK_CONTAINER(sw), text);
+  g_signal_connect(text, "size-allocate",
+                   G_CALLBACK(main_message_area_size_allocate), NULL);
 
   gtk_widget_set_name(text, "chatline");
 
diff --git a/client/gui-gtk-2.0/pages.c b/client/gui-gtk-2.0/pages.c
index a0f138b..d40a04b 100644
--- a/client/gui-gtk-2.0/pages.c
+++ b/client/gui-gtk-2.0/pages.c
@@ -2223,7 +2223,10 @@ void set_client_page(enum client_pages page)
 
   switch (new_page) {
   case PAGE_MAIN:
+    break;
   case PAGE_START:
+    chatline_scroll_to_bottom();
+    break;
   case PAGE_GGZ:
     break;
   case PAGE_NATION:
@@ -2236,6 +2239,7 @@ void set_client_page(enum client_pages page)
     gtk_tree_view_focus(gtk_tree_selection_get_tree_view(scenario_selection));
     break;
   case PAGE_GAME:
+    chatline_scroll_to_bottom();
     break;
   case PAGE_NETWORK:
     update_network_lists();
_______________________________________________
Freeciv-dev mailing list
Freeciv-dev@gna.org
https://mail.gna.org/listinfo/freeciv-dev

Reply via email to