All pastes #631759 Raw Edit

Miscellany

public text v1 · immutable
#631759 ·published 2007-07-24 09:35 UTC
rendered paste body
/*
 * This file part of StarDict - A international dictionary for GNOME.
 * http://stardict.sourceforge.net
 * Copyright (C) 2005-2006 Evgeniy <dushistov@mail.ru>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "gtktextviewpango.h"
#include "utils.h"
#include "skin.h"//for SkinCursor definition

#include "pangoview.h"

class TextPangoWidget : public PangoWidgetBase {
public:
	TextPangoWidget();
	GtkWidget *widget() { return GTK_WIDGET(textview_); }

	void clear();
	void append_mark(const char *mark);
	void append_pixbuf(GdkPixbuf *pixbuf, const char *label);
	void begin_update();
	void end_update();
	std::string get_text();
	void append_pango_text_with_links(const std::string&,
					  const LinksPosList&);
protected:
	void do_set_text(const char *str);
	void do_append_text(const char *str);
	void do_append_pango_text(const char *str);
	void do_set_pango_text(const char *str);
private:
	GtkTextView *textview_;
	std::list<GtkTextMark *> marklist_;
	struct TextBufPos {
		gint beg_;
		gint end_;
		std::string link_;
		TextBufPos(gint beg, gint end, std::string link): beg_(beg), end_(end), link_(link) {}
	};
	typedef std::vector<TextBufPos> TextBufLinks;

	TextBufLinks tb_links_;
	GtkTextIter iter_;
	SkinCursor hand_cursor_, regular_cursor_;

	static gboolean on_mouse_move(GtkWidget *, GdkEventMotion *, gpointer);
	static gboolean on_button_release(GtkWidget *, GdkEventButton *, gpointer);

	void goto_begin();
	void goto_end();
	TextBufLinks::const_iterator find_link(gint x, gint y);
};

class LabelPangoWidget : public PangoWidgetBase {
public:
	LabelPangoWidget();
	GtkWidget *widget() { return GTK_WIDGET(label_); }

	void clear();
	void append_mark(const char *mark) {}
	void append_pixbuf(GdkPixbuf *pixbuf, const char *label);
	std::string get_text();
	void modify_bg(GtkStateType state, const GdkColor *color);
protected:
	void do_set_text(const char *str);
	void do_append_text(const char *str);
	void do_append_pango_text(const char *str);
	void do_set_pango_text(const char *str);
private:
	GtkLabel *label_;
	GtkWidget *viewport_;
};


void PangoWidgetBase::begin_update()
{
	update_ = true;
}

void PangoWidgetBase::end_update()
{
	if (update_) {
		update_ = false;
		do_append_pango_text(cache_.c_str());
		cache_.clear();
	}
}

void PangoWidgetBase::append_text(const char *str)
{
	if (update_)
		cache_ += str;
	else
		do_append_text(str);
}

void PangoWidgetBase::append_pango_text(const char *str)
{
	if (update_)
		cache_ += str;
	else
		do_append_pango_text(str);
}

void PangoWidgetBase::append_pango_text_with_links(const std::string& str,
						   const LinksPosList&)
{
	append_pango_text(str.c_str());
}

void PangoWidgetBase::set_pango_text(const char *str)
{
	if (update_)
		cache_ = str;
	else
		do_set_pango_text(str);
}


void TextPangoWidget::begin_update()
{
	PangoWidgetBase::begin_update();
	gtk_text_buffer_begin_user_action(
		gtk_text_view_get_buffer(textview_));
}


void TextPangoWidget::end_update()
{
	PangoWidgetBase::end_update();
	gtk_text_buffer_end_user_action(gtk_text_view_get_buffer(textview_));
}


TextPangoWidget::TextPangoWidget()
{
	hand_cursor_.reset(gdk_cursor_new(GDK_HAND2));
	regular_cursor_.reset(gdk_cursor_new(GDK_XTERM));
	textview_ = GTK_TEXT_VIEW(gtk_text_view_new());
	gtk_widget_show(GTK_WIDGET(textview_));
	gtk_text_view_set_editable(textview_, FALSE);
	gtk_text_view_set_cursor_visible(textview_, FALSE);
	gtk_text_view_set_wrap_mode(textview_, GTK_WRAP_WORD_CHAR);
	gtk_text_view_set_left_margin(textview_, 5);
	gtk_text_view_set_right_margin(textview_, 5);

	g_signal_connect(textview_, "button-release-event",
			 G_CALLBACK(on_button_release), this);
	g_signal_connect(textview_, "motion-notify-event",
			 G_CALLBACK(on_mouse_move), this);
	
	gtk_text_buffer_get_iter_at_offset(gtk_text_view_get_buffer(textview_),
					   &iter_, 0);
	scroll_win_ = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
	gtk_widget_show(GTK_WIDGET(scroll_win_));


	gtk_scrolled_window_set_policy(scroll_win_,
				       //altought textview's set_wrap_mode will cause
				       //this can be GTK_POLICY_NEVER,but...
				       //there are widgets that may make this broken.
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
	gtk_container_add(GTK_CONTAINER(scroll_win_), GTK_WIDGET(textview_));
	gtk_scrolled_window_set_shadow_type(scroll_win_, GTK_SHADOW_IN);
}

void LabelPangoWidget::modify_bg(GtkStateType state, const GdkColor *color)
{
	gtk_widget_modify_bg(viewport_, state, color);
}

LabelPangoWidget::LabelPangoWidget()
{
    label_ = GTK_LABEL(gtk_label_new(NULL));
    gtk_label_set_justify(label_, GTK_JUSTIFY_LEFT);
    scroll_win_ = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
    gtk_scrolled_window_set_shadow_type(scroll_win_, GTK_SHADOW_NONE);
    gtk_scrolled_window_set_policy(scroll_win_, GTK_POLICY_NEVER,
				   GTK_POLICY_AUTOMATIC);

    viewport_ =
	    gtk_viewport_new(gtk_scrolled_window_get_hadjustment(scroll_win_),
			     gtk_scrolled_window_get_vadjustment(scroll_win_));
    gtk_widget_add_events(viewport_, GDK_BUTTON1_MOTION_MASK);
    gtk_widget_add_events(viewport_, GDK_BUTTON_RELEASE_MASK);
    gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport_), GTK_SHADOW_NONE);
    gtk_container_add(GTK_CONTAINER(scroll_win_), viewport_);
    gtk_container_add(GTK_CONTAINER(viewport_), GTK_WIDGET(label_));
}

PangoWidgetBase *PangoWidgetBase::create(bool autoresize)
{
	if (!autoresize)
		return new TextPangoWidget;
	else
		return new LabelPangoWidget;
}

void TextPangoWidget::do_set_text(const char *text)
{
	GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview_);

	std::list<GtkTextMark *>::const_iterator it;
	for (it = marklist_.begin(); it != marklist_.end(); ++it)
		gtk_text_buffer_delete_mark(buffer, *it);
	marklist_.clear();
	tb_links_.clear();

	gtk_text_buffer_set_text(buffer, text, -1);
}

void LabelPangoWidget::do_set_text(const char *text)
{
	scroll_to(0);
	// this should speed up the next two line.
	gtk_label_set_markup(label_, "");
	// so Popup()'s gtk_widget_size_request(label, &requisition); can
	gtk_widget_set_size_request(GTK_WIDGET(label_), -1, -1);
	// get its original width.
	gtk_label_set_line_wrap(label_, FALSE);
	gchar *mstr = g_markup_escape_text(text, -1);
	gtk_label_set_text(label_, mstr);
	g_free(mstr);
}

void LabelPangoWidget::append_pixbuf(GdkPixbuf *pixbuf, const char *label)
{
	if (label) {
		std::string str;
		str = "<span foreground=\"red\">[Image:";
		str += label;
		str += "]</span>";
		append_pango_text(str.c_str());
	} else {
		append_pango_text("<span foreground=\"red\">[Image]</span>");
	}
}

void PangoWidgetBase::set_text(const char *str)
{
	if (update_)
		cache_ = str;
	else
		do_set_text(str);
}

void TextPangoWidget::do_append_text(const char *str)
{
	gtk_text_buffer_insert(gtk_text_view_get_buffer(textview_),
			       &iter_, str, strlen(str));
}

void LabelPangoWidget::do_append_text(const char *str)
{
	set_text((std::string(gtk_label_get_text(label_)) + str).c_str());
}


void TextPangoWidget::do_append_pango_text(const char *str)
{

    gtk_text_buffer_insert_markup(gtk_text_view_get_buffer(textview_),
				  &iter_, str);
}

void TextPangoWidget::do_set_pango_text(const char *str)
{
	clear();
	goto_begin();
	do_append_pango_text(str);
}

void LabelPangoWidget::do_set_pango_text(const char *str)
{
	scroll_to(0);
	// this should speed up the next two line.
	gtk_label_set_markup(label_, "");
	// so Popup()'s gtk_widget_size_request(label,&requisition); can
	gtk_widget_set_size_request(GTK_WIDGET(label_), -1, -1);
	// get its original width.
	gtk_label_set_line_wrap(label_, FALSE);
	gtk_label_set_markup(label_, str);
}

void LabelPangoWidget::do_append_pango_text(const char *str)
{
   do_set_pango_text((std::string(gtk_label_get_label(label_)) + str).c_str());
}

void TextPangoWidget::append_mark(const char *mark)
{
	GtkTextBuffer *buffer=gtk_text_view_get_buffer(textview_);
	if (update_) {
		if (!cache_.empty()) {
			do_append_pango_text(cache_.c_str());
			cache_.clear();
		}
	}
	marklist_.push_back(
		gtk_text_buffer_create_mark(buffer, mark, &iter_, TRUE));
}

void TextPangoWidget::clear()
{
	GtkTextBuffer *buffer = gtk_text_view_get_buffer(textview_);

	std::list<GtkTextMark *>::const_iterator it;
	for (it = marklist_.begin(); it != marklist_.end(); ++it)
		gtk_text_buffer_delete_mark(buffer, *it);

	marklist_.clear();
	tb_links_.clear();

	GtkTextIter start, end;
	gtk_text_buffer_get_bounds(buffer, &start, &end);
	gtk_text_buffer_delete(buffer, &start, &end);
	scroll_to(0);
	cache_.clear();
}

void LabelPangoWidget::clear()
{
	do_set_text("");
	cache_.clear();
}

void TextPangoWidget::goto_begin()
{
	gtk_text_buffer_get_iter_at_offset(
		gtk_text_view_get_buffer(textview_), &iter_, 0
		);
}

void TextPangoWidget::goto_end()
{
    gtk_text_buffer_get_iter_at_offset(gtk_text_view_get_buffer(textview_), &iter_, -1);
}

std::string TextPangoWidget::get_text()
{
	std::string res;

	GtkTextIter start, end;
	GtkTextBuffer *buffer=gtk_text_view_get_buffer(textview_);
	gtk_text_buffer_get_bounds(buffer, &start, &end);
	gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
	res = text;
	g_free(text);

	return res;
}

std::string LabelPangoWidget::get_text()
{
	return gtk_label_get_text(label_);
}


void TextPangoWidget::append_pango_text_with_links(const std::string& str,
						   const LinksPosList& links)
{
	if (links.empty()) {
		append_pango_text(str.c_str());
		return;
	}

	do_append_pango_text(cache_.c_str());
	cache_.clear();

	gint beg = gtk_text_iter_get_offset(&iter_);

	for (LinksPosList::const_iterator it = links.begin();
	     it != links.end(); ++it) {
		tb_links_.push_back(TextBufPos(beg + it->pos_, beg + it->pos_ + it->len_, it->link_));	
	}

	gtk_text_buffer_insert_markup(gtk_text_view_get_buffer(textview_),
				      &iter_, str.c_str());
}

void TextPangoWidget::append_pixbuf(GdkPixbuf *pixbuf, const char *label)
{
	do_append_pango_text(cache_.c_str());
	cache_.clear();
	gtk_text_buffer_insert_pixbuf (gtk_text_view_get_buffer(textview_), &iter_, pixbuf);
}

TextPangoWidget::TextBufLinks::const_iterator TextPangoWidget::find_link(gint x,
									 gint y)
{
	GtkTextIter iter;
	gtk_text_view_get_iter_at_location(textview_, &iter, x, y);
	gint pos = gtk_text_iter_get_offset(&iter);
	TextBufLinks::const_iterator it;
	for (it = tb_links_.begin(); it != tb_links_.end(); ++it) {
		if (pos < it->beg_)
			return tb_links_.end();
		if (it->beg_ <= pos && pos < it->end_) 
			break;
	}
	return it;
}

gboolean TextPangoWidget::on_mouse_move(GtkWidget *widget, GdkEventMotion *event,
					gpointer userdata)
{
	TextPangoWidget *tpw = static_cast<TextPangoWidget *>(userdata);
	GtkTextWindowType win_type =
		gtk_text_view_get_window_type(tpw->textview_, event->window);
	gint x, y;
	gtk_text_view_window_to_buffer_coords(tpw->textview_, win_type, 
					      gint(event->x), gint(event->y),
					      &x, &y);

	TextBufLinks::const_iterator it = tpw->find_link(x, y);
	if (it != tpw->tb_links_.end()) {
		gdk_window_set_cursor(
			gtk_text_view_get_window(tpw->textview_,
						 GTK_TEXT_WINDOW_TEXT),
			get_impl(tpw->hand_cursor_));
	} else {
		gdk_window_set_cursor(
			gtk_text_view_get_window(tpw->textview_,
						 GTK_TEXT_WINDOW_TEXT),
			get_impl(tpw->regular_cursor_));
	}

	gdk_window_get_pointer(widget->window, NULL, NULL, NULL);

	return FALSE;
}



gboolean TextPangoWidget::on_button_release(GtkWidget *, GdkEventButton *event,
					    gpointer userdata)
{
	if (event->button != 1)
		return FALSE;
	TextPangoWidget *tpw = static_cast<TextPangoWidget *>(userdata);
	GtkTextBuffer *buf = gtk_text_view_get_buffer(tpw->textview_);
	/* we shouldn't follow a link if the user has selected something */
	GtkTextIter beg, end;
	gtk_text_buffer_get_selection_bounds (buf, &beg, &end);
	if (gtk_text_iter_get_offset (&beg) != gtk_text_iter_get_offset (&end))
		return FALSE;
	GtkTextWindowType win_type =
		gtk_text_view_get_window_type(tpw->textview_, event->window);
	gint x, y;
	gtk_text_view_window_to_buffer_coords(tpw->textview_, win_type, 
					      gint(event->x), gint(event->y),
					      &x, &y);
	TextBufLinks::const_iterator it = tpw->find_link(x, y);
	if (it != tpw->tb_links_.end()) {
		tpw->on_link_click_.emit(it->link_);
	}	
	return FALSE;
}