This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/widgets/gm-text-scroller.c

329 lines
8.9 KiB
C

#include <glib-object.h>
#include <gtk/gtk.h>
#include <string.h>
#include <stdlib.h>
#include "gm-text-scroller.h"
#include "gm-debug.h"
#include "gm-support.h"
#define GM_TEXT_SCROLLER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_TEXT_SCROLLER, GmTextScrollerPrivate))
struct _GmTextScrollerPrivate {
GtkTextView *text_view;
GtkTextBuffer *text_buffer;
GtkScrolledWindow *scrolled_window;
gint character_height;
gboolean end_scrolled;
gint idle_handler;
GtkAllocation allocation;
};
void on_gm_text_scroller_text_view_style_set(GtkTextView *view,
GtkStyle *previous_style, GmTextScroller *scroller);
void on_gm_text_scroller_text_buffer_changed(GtkTextBuffer *text_buffer,
GmTextScroller *scroller);
void on_gm_text_scroller_text_view_notify(GtkTextView *text_view, GParamSpec *arg1,
GmTextScroller *scroller);
void on_gm_text_scroller_text_view_destroy(GtkTextView *text_view,
GmTextScroller *scroller);
/* Signals
enum {
PROTO
NUM_SIGNALS
};
static guint gm_text_scroller_signals[NUM_SIGNALS] = {0};*/
G_DEFINE_TYPE(GmTextScroller, gm_text_scroller, G_TYPE_OBJECT)
static void
gm_text_scroller_finalize(GObject *object) {
GmTextScroller *obj = GM_TEXT_SCROLLER(object);
if (obj->priv->text_buffer != NULL) {
g_signal_handlers_disconnect_by_func(obj->priv->text_buffer,
G_CALLBACK(on_gm_text_scroller_text_buffer_changed),
obj);
g_object_unref(obj->priv->text_buffer);
}
if (obj->priv->idle_handler) {
g_source_remove(obj->priv->idle_handler);
}
g_signal_handlers_disconnect_by_func(obj->priv->text_view,
G_CALLBACK(on_gm_text_scroller_text_view_notify), obj);
g_signal_handlers_disconnect_by_func(obj->priv->text_view,
G_CALLBACK(on_gm_text_scroller_text_view_style_set), obj);
g_signal_handlers_disconnect_by_func(obj->priv->text_view,
G_CALLBACK(on_gm_text_scroller_text_view_destroy), obj);
G_OBJECT_CLASS(gm_text_scroller_parent_class)->finalize(object);
}
static void
gm_text_scroller_class_init(GmTextScrollerClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->finalize = gm_text_scroller_finalize;
/*gm_text_scroller_signals[PROTO] =
g_signal_new("proto",
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(GmTextScrollerClass, proto),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);*/
g_type_class_add_private(object_class, sizeof(GmTextScrollerPrivate));
}
static void
gm_text_scroller_init(GmTextScroller *obj) {
obj->priv = GM_TEXT_SCROLLER_GET_PRIVATE(obj);
obj->priv->character_height = 10;
obj->priv->end_scrolled = FALSE;
}
void
gm_text_scroller_update_text_buffer(GmTextScroller *scroller) {
if (scroller->priv->text_buffer != NULL) {
g_signal_handlers_disconnect_by_func(scroller->priv->text_buffer,
G_CALLBACK(on_gm_text_scroller_text_buffer_changed),
scroller);
g_object_unref(scroller->priv->text_buffer);
}
scroller->priv->text_buffer =
gtk_text_view_get_buffer(scroller->priv->text_view);
if (scroller->priv->text_buffer != NULL) {
g_object_ref(scroller->priv->text_buffer);
g_signal_connect(scroller->priv->text_buffer, "changed",
G_CALLBACK(on_gm_text_scroller_text_buffer_changed), scroller);
}
}
void
gm_text_scroller_scroll_end(GmTextScroller *scroller) {
GtkTextMark *mark;
GtkTextIter iter;
g_return_if_fail(GTK_IS_TEXT_BUFFER(scroller->priv->text_buffer));
mark = gtk_text_buffer_get_mark(scroller->priv->text_buffer,
"end-of-buffer");
if (mark == NULL) {
gtk_text_buffer_get_end_iter(scroller->priv->text_buffer, &iter);
mark = gtk_text_buffer_create_mark(scroller->priv->text_buffer,
"end-of-buffer", &iter, FALSE);
}
gtk_text_view_scroll_to_mark(scroller->priv->text_view, mark, 0.0,
TRUE, 1.0, 1.0);
}
void
gm_text_scroller_scroll_begin(GmTextScroller *scroller) {
GtkTextMark *mark;
GtkTextIter iter;
g_return_if_fail(GTK_IS_TEXT_BUFFER(scroller->priv->text_buffer));
mark = gtk_text_buffer_get_mark(scroller->priv->text_buffer,
"begin-of-buffer");
if (mark == NULL) {
gtk_text_buffer_get_start_iter(scroller->priv->text_buffer, &iter);
mark = gtk_text_buffer_create_mark(scroller->priv->text_buffer,
"begin-of-buffer", &iter, TRUE);
}
gtk_text_view_scroll_to_mark(scroller->priv->text_view, mark, 0.0,
TRUE, 0.0, 0.0);
}
gboolean
gm_text_scroller_scroll_end_idle(GmTextScroller *scroller) {
scroller->priv->idle_handler = 0;
gm_text_scroller_scroll_end(scroller);
scroller->priv->end_scrolled = FALSE;
return FALSE;
}
gboolean
gm_text_scroller_is_end_scrolled_margin(GmTextScroller *scroller,
gint margin) {
GtkAdjustment *ad = gtk_scrolled_window_get_vadjustment(
scroller->priv->scrolled_window);
return scroller->priv->end_scrolled ||
((ad->page_size + ad->value) >= ad->upper -
(double)(scroller->priv->character_height) - margin);
}
gboolean
gm_text_scroller_is_end_scrolled(GmTextScroller *scroller) {
return gm_text_scroller_is_end_scrolled_margin(scroller, 0);
}
void
gm_text_scroller_prepare(GmTextScroller *scroller) {
scroller->priv->end_scrolled = gm_text_scroller_is_end_scrolled(scroller);
if (scroller->priv->idle_handler == 0 && scroller->priv->end_scrolled) {
gm_text_scroller_scroll_end(scroller);
// Ensure the end scroll for large texts
scroller->priv->idle_handler = g_idle_add((GSourceFunc)
gm_text_scroller_scroll_end_idle, scroller);
}
}
void
gm_text_scroller_update_character_height(GmTextScroller *scroller) {
GtkRcStyle *style = gtk_widget_get_modifier_style(GTK_WIDGET(
scroller->priv->text_view));
PangoContext *pc = gtk_widget_create_pango_context(GTK_WIDGET(
scroller->priv->text_view));
PangoLayout *pl;
gint cwidth, cheight;
if (style->font_desc != NULL) {
pango_context_set_font_description(pc, style->font_desc);
pl = pango_layout_new(pc);
pango_layout_set_text(pl, "G", 1);
pango_layout_get_pixel_size(pl, &(cwidth),
&(cheight));
if (scroller->priv->character_height != cheight) {
scroller->priv->character_height = cheight;
}
g_object_unref(pl);
}
g_object_unref(pc);
}
void
on_gm_text_scroller_text_view_size_allocate(GtkWidget *widget,
GtkAllocation *allocation, GmTextScroller *scroller) {
if (gm_text_scroller_is_end_scrolled_margin(scroller,
abs(allocation->height - scroller->priv->allocation.height))) {
if (scroller->priv->idle_handler == 0) {
gm_text_scroller_scroll_end(scroller);
// Ensure the end scroll for large texts
scroller->priv->idle_handler = g_idle_add((GSourceFunc)
gm_text_scroller_scroll_end_idle, scroller);
}
} else {
GM_DEBUG("Size allocate");
}
scroller->priv->allocation = *allocation;
}
GmTextScroller *
gm_text_scroller_new(GtkTextView *text_view) {
GmTextScroller *obj;
GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(text_view));
if (parent == NULL || !GTK_IS_SCROLLED_WINDOW(parent)) {
return NULL;
}
obj = GM_TEXT_SCROLLER(g_object_new(GM_TYPE_TEXT_SCROLLER,
NULL));
obj->priv->text_view = text_view;
obj->priv->scrolled_window = GTK_SCROLLED_WINDOW(parent);
obj->priv->allocation = (GTK_WIDGET(text_view)->allocation);
gm_text_scroller_update_text_buffer(obj);
gm_text_scroller_update_character_height(obj);
g_signal_connect(text_view, "notify::buffer",
G_CALLBACK(on_gm_text_scroller_text_view_notify), obj);
g_signal_connect(text_view, "style-set",
G_CALLBACK(on_gm_text_scroller_text_view_style_set), obj);
g_signal_connect(text_view, "destroy",
G_CALLBACK(on_gm_text_scroller_text_view_destroy), obj);
g_signal_connect(text_view, "size-allocate",
G_CALLBACK(on_gm_text_scroller_text_view_size_allocate), obj);
obj->priv->idle_handler = g_idle_add((GSourceFunc)
gm_text_scroller_scroll_end_idle, obj);
return obj;
}
void
gm_text_scroller_scroll_page(GmTextScroller *scroller, gint direction) {
GtkScrolledWindow *srl = scroller->priv->scrolled_window;
GtkAdjustment *adj = gtk_scrolled_window_get_vadjustment(srl);
double value = adj->value + (adj->page_size * direction);
if (value > adj->upper - adj->page_size) {
gtk_adjustment_set_value(adj, adj->upper - adj->page_size);
} else if (value < adj->lower) {
gtk_adjustment_set_value(adj, adj->lower);
} else {
gtk_adjustment_set_value(adj, value);
}
}
// Callbacks
void
on_gm_text_scroller_text_view_notify(GtkTextView *text_view, GParamSpec *arg1,
GmTextScroller *scroller) {
// Buffer changed
gm_text_scroller_update_text_buffer(scroller);
}
void
on_gm_text_scroller_text_view_style_set(GtkTextView *view,
GtkStyle *previous_style, GmTextScroller *scroller) {
g_return_if_fail(GM_IS_TEXT_SCROLLER(scroller));
gm_text_scroller_update_character_height(scroller);
}
void
on_gm_text_scroller_text_buffer_changed(GtkTextBuffer *text_buffer,
GmTextScroller *scroller) {
g_return_if_fail(GM_IS_TEXT_SCROLLER(scroller));
gm_text_scroller_prepare(scroller);
}
void
on_gm_text_scroller_text_view_destroy(GtkTextView *text_view,
GmTextScroller *scroller) {
// Remove ourselfs when the text view dies
g_object_unref(scroller);
}