GTK v1.3.2: New text widgets



Guys (and especially Havoc),

I got hold of a copy of GTK+ v1.3.2 over the weekend and ported my message
module to Havoc's new text widget API. (Very nice work btw, Havoc, most
impressed.) I found I had to use a bit of a hack to get it to do what I wanted
however, so I wondered if the API might need extending slightly (or if I've
just missed something).

My message module is part of a plug-in windowing interface to a roguelike game
(Slash'EM, homepage http://slashem.sourceforge.net). Because the game itself is
not event driven, gtk_main() is only called when the game requests input (and
gtk_main_quit() is called in the relevant signal handlers).

The message module is used to display textual output from the game. The
putstr() function may be called any number of times between requests for input
(and therefore calls to gtk_main). The output should be displayed in a window
which can be scrolled back by the user to see what occured a little while ago,
but should automatically revert to displaying the most recent line whenever new
text is added. The scrollbar should be displayed as fully scrolled-down at this
point, so that the user has confidence that there are no more messages
off-screen.

Text should be displayed in red if the character is considered "in danger" and
trimmed from the top so that the memory requirements don't keep on growing
indefinitely.

This was all easy to implement except for managing the scrolling. I've got my
own mark for our insertion point and so I thought I should just be able to call
scroll_to_mark() at the end of each putstr() function. This works if putstr()
is only called once between calls to gtk_main(), but fails otherwise (it seems
only the first call is actioned). I ended up having to use the adjustment
changed signal to trap when the actual view is being updated and calling
scroll_to_mark() then, but it seems a little gross.

I imagine I wouldn't be having these problems if I was in gtk_main() all the
time, but that's not an option available to me. I suggest that a signal be
added to GtkTextView which is emitted when the view is being updated, rather
than having to hijack adjustment changed for this purpose.

Many thanks,

-- 
Ali Harlow                              Email: ali avrc city ac uk
Research programmer                     Tel:   (020) 7477 8000 X 4348
Applied Vision Research Centre          Intl: +44 20 7477 8000 X 4348
City University                         Fax:   (020) 7505 5515
London                                  Intl: +44 20 7505 5515
/*
  $Id: gtkmessage.c,v 1.6 2000/12/29 17:12:53 j_ali Exp $
 */
/*
  GTK+ NetHack Copyright (c) Issei Numata 1999-2000
               Copyright (c) Slash'EM Development Team 2000-2001
  GTK+ NetHack may be freely redistributed.  See license for details. 
*/

#include <sys/types.h>
#include <signal.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "winGTK.h"

static boolean in_danger(void)
{
    return (u.uhpmax > 0 && (((double)u.uhp) / u.uhpmax < 0.1 || u.uhp < 5));
}

#ifdef GTK_TEXT_VIEW_H

/*
 * The new implementation using GtkTextView, which should solve a number
 * of problems, particuarly with the win32 port. Note that at the time of
 * writing, gimpwin does not yet include GtkTextView.
 */

static GtkWidget	*message_view;
static GtkWidget	*message_sw;
static GtkTextBuffer	*message_buffer;
static GtkTextMark	*message_cursor;
static GtkTextTag	*message_danger_tag;
static gint		message_line_count, message_adj_line_count;
static gint		message_char_count;

/*
 * By delaying the scroll_to_mark until the view is actually updated
 * (of which changing the vertical adjustment is a side effect), we
 * optimise the case of two calls to putstr() between calls to gtk_main()
 * plus also work around a slight oddity of GTK v1.3.2 which appears
 * to ignore the second and subsequent calls to gtk_text_view_scroll_to_mark().
 */

static void nh_message_adjustment_changed(GtkAdjustment *adj, GtkTextView *view)
{
    /* gtk_text_view_scroll_to_mark() doesn't work (and isn't needed)
     * if the view isn't mapped yet.
     */
    if (message_cursor && GTK_WIDGET_MAPPED(GTK_WIDGET(view))) {
	gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(view), message_cursor, 0);
	message_adj_line_count = message_line_count;
    }
}

static void nh_message_set_scroll_adjustments(GtkTextView *view, GtkAdjustment *hadj, GtkAdjustment *vadj)
{
   if (vadj)
	gtk_signal_connect(GTK_OBJECT(vadj), "changed",
	  (GtkSignalFunc)nh_message_adjustment_changed, view);
}

GtkWidget *
nh_message_new()
{
    GtkTextIter iter;
    message_sw = gtk_scrolled_window_new(NULL, NULL);
    GTK_WIDGET_UNSET_FLAGS(message_sw, GTK_CAN_FOCUS);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(message_sw),
      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    message_view = gtk_text_view_new();
    GTK_WIDGET_UNSET_FLAGS(message_view, GTK_CAN_FOCUS);
    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(message_view), GTK_WRAPMODE_WORD);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(message_view), FALSE);
    gtk_signal_connect_after(GTK_OBJECT(message_view), "set_scroll_adjustments",
      GTK_SIGNAL_FUNC(nh_message_set_scroll_adjustments), 0);
    message_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(message_view));
    /*
     * Use our own cursor mark so that we don't have to worry about it moving
     * if users click on the widget. This also has the nice side effect that
     * users can select an area of text without interference from us.
     */
    gtk_text_buffer_get_iter_at_offset(message_buffer, &iter, 0);
    message_cursor = gtk_text_buffer_create_mark(message_buffer, "nh_insert",
      &iter, FALSE);
    gtk_container_add(GTK_CONTAINER(message_sw), message_view);
    message_danger_tag = gtk_text_buffer_create_tag(message_buffer, "danger");
    gtk_object_set(GTK_OBJECT(message_danger_tag), "foreground", "red", NULL);
    message_char_count = gtk_text_buffer_get_char_count(message_buffer);
    return message_sw;
}

void
nh_message_putstr(const char *str)
{
    int len, line;
    char *buf;
    GtkTextIter iter, iter2;

    len = strlen(str);
    buf = (char *)alloc(len + 2);
    sprintf(buf, "\n%s", str);
    gtk_text_buffer_get_iter_at_mark(message_buffer, &iter, message_cursor);
    /* Assume buf is in ASCII (and therefore valid UTF-8) */
    gtk_text_buffer_insert_with_tags(message_buffer, &iter, buf, len + 1,
      in_danger() ? message_danger_tag : NULL, NULL);
    free(buf);
    message_char_count += len + 1;
    message_line_count = gtk_text_buffer_get_line_count(message_buffer);
    if (message_char_count > NH_TEXT_REMEMBER) {
	gtk_text_buffer_get_iter_at_offset(message_buffer, &iter, 0);
	gtk_text_buffer_get_iter_at_offset(message_buffer, &iter2,
	  message_char_count - NH_TEXT_REMEMBER);
	line = gtk_text_iter_get_line(&iter2);
	/*
	 * Don't trim line if that would leave us with the same number of
	 * lines as last time. This guarantees that the vertical adjustment
	 * will change and thus nh_message_adjustment_changed() will be called.
	 */
	if (message_line_count - line == message_adj_line_count)
	    line--;
	if (line > 0) {
	    gtk_text_buffer_get_iter_at_line(message_buffer, &iter2, line);
	    message_char_count -= gtk_text_iter_get_offset(&iter2);
	    message_line_count -= line;
	    gtk_text_buffer_delete(message_buffer, &iter, &iter2);
	}
    }
}

#else /* GTK_TEXT_VIEW_H */

/*
 * The old implementation using GtkText (GtkTextView is not available in
 * version 1.2.x of GTK+).
 */

static GtkWidget	*message_h;
static GtkWidget	*message_hbox;
static GtkWidget	*message_text;
static GtkWidget	*message_scrollbar;

extern int		root_width;
extern int		root_height;

#ifdef WIN32
/* ALI: Switching this on avoids strange scrolling effects (see bug 124233),
 * but causes an assertion failure in gimpwin the first time we trim the
 * old text.
 */
/* #define ALWAYS_FREEZE */
#endif

GtkWidget *
nh_message_new()
{
    message_h = gtk_handle_box_new();
    GTK_HANDLE_BOX(message_h)->shrink_on_detach = 1;

    message_hbox = nh_gtk_new_and_add(gtk_hbox_new(FALSE, 0), message_h, "");
    
    message_text = nh_gtk_new_and_pack(
	gtk_text_new(NULL, NULL), message_hbox, "",
	TRUE, TRUE, NH_PAD);

    GTK_WIDGET_UNSET_FLAGS(message_text, GTK_CAN_FOCUS);
    gtk_text_set_word_wrap((GtkText *) message_text, TRUE);
  
    message_scrollbar = nh_gtk_new_and_pack(
	gtk_vscrollbar_new(GTK_TEXT(message_text)->vadj), message_hbox, "",
	FALSE, FALSE, NH_PAD);
    
    return message_h;
}

void
nh_message_putstr(const char *str)
{
  int		i;
  int		len;
  char		*buf;
  GtkText	*t;

  t = GTK_TEXT(message_text);

  len = strlen(str);
  buf = (char *)alloc(len + 2);

  sprintf(buf, "\n%s", str);

#ifdef ALWAYS_FREEZE
  /* ALI: gimpwin 20001226 looks very bad if you update a text widget without
   * freezing it (the text is displayed half-scrolled, with lines overlapping
   * each other). This is not ideal (the text is redrawn each thaw), but it
   * is an improvement.
   */
  gtk_text_freeze(t);
#endif

  if (in_danger())
    i = MAP_RED;
  else
    i = MAP_BLACK;
  gtk_text_insert(t, NULL, &nh_color[i], &nh_color[MAP_WHITE], buf, len + 1);
  
  len = gtk_text_get_length(t);
  if(len > NH_TEXT_REMEMBER){
    for(i=0 ; i<len ; ++i)
      if(GTK_TEXT_INDEX(t, i) == '\n'){
	++i;
#ifndef ALWAYS_FREEZE
	gtk_text_freeze(t);
#endif
	gtk_text_set_point(t, i);
	gtk_text_backward_delete(t, i);
	gtk_text_set_point(t, len - i);
#ifndef ALWAYS_FREEZE
	gtk_text_thaw(t);
#endif
	break;
      }
  }
#ifdef ALWAYS_FREEZE
  /* ALI: t->vadj->upper would be more correct, but causes gimpwin to crash */
  gtk_adjustment_set_value(t->vadj, t->vadj->upper - 1);
  gtk_text_thaw(t);
#endif

  free(buf);
}

#endif /* GTK_TEXT_VIEW_H */

int
GTK_doprev_message()
{
  return 0;
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]