#include #include #include #include #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); } } 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); }