diff options
Diffstat (limited to 'lib')
6 files changed, 362 insertions, 77 deletions
diff --git a/lib/gtk-experiment-widgets/Makefile.am b/lib/gtk-experiment-widgets/Makefile.am index ab05c9a..57390db 100644 --- a/lib/gtk-experiment-widgets/Makefile.am +++ b/lib/gtk-experiment-widgets/Makefile.am @@ -2,18 +2,26 @@ AM_CFLAGS = -Wall BUILT_SOURCES = cclosure-marshallers.c cclosure-marshallers.h +noinst_LTLIBRARIES = libgtk-experiment-transcript.la +libgtk_experiment_transcript_la_SOURCES = gtk-experiment-transcript.h \ + gtk-experiment-transcript-private.h \ + gtk-experiment-transcript.c \ + gtk-experiment-transcript-formats.c + +libgtk_experiment_transcript_la_CFLAGS = $(AM_CFLAGS) \ + @LIBGTK_CFLAGS@ +libgtk_experiment_transcript_la_CPPFLAGS = -I@top_srcdir@/lib/experiment-reader + lib_LTLIBRARIES = libgtk-experiment-widgets.la -libgtk_experiment_widgets_la_SOURCES = gtk-experiment-navigator.c \ - gtk-experiment-navigator.h \ - gtk-experiment-transcript.c \ - gtk-experiment-transcript.h +libgtk_experiment_widgets_la_SOURCES = gtk-experiment-navigator.h \ + gtk-experiment-navigator.c nodist_libgtk_experiment_widgets_la_SOURCES = $(BUILT_SOURCES) libgtk_experiment_widgets_la_CFLAGS = $(AM_CFLAGS) libgtk_experiment_widgets_la_CPPFLAGS = libgtk_experiment_widgets_la_LDFLAGS = -no-undefined -shared -bindir @bindir@ \ -avoid-version -libgtk_experiment_widgets_la_LIBADD = +libgtk_experiment_widgets_la_LIBADD = libgtk-experiment-transcript.la libgtk_experiment_widgets_la_CFLAGS += @LIBGTK_CFLAGS@ libgtk_experiment_widgets_la_LIBADD += @LIBGTK_LIBS@ @@ -21,14 +29,15 @@ libgtk_experiment_widgets_la_LIBADD += @LIBGTK_LIBS@ libgtk_experiment_widgets_la_CPPFLAGS += -I@top_srcdir@/lib/experiment-reader libgtk_experiment_widgets_la_LIBADD += @top_srcdir@/lib/experiment-reader/libexperiment-reader.la -include_HEADERS = gtk-experiment-navigator.h +include_HEADERS = gtk-experiment-navigator.h \ + gtk-experiment-transcript.h dist_gtk_experiment_widgets_catalogs_DATA = gtk-experiment-widgets-catalog.xml dist_noinst_DATA = cclosure-marshallers.list CLEANFILES = $(BUILT_SOURCES) -MARSHAL_PREFIX = gtk_experiment_navigator_marshal +MARSHAL_PREFIX = gtk_experiment_widgets_marshal cclosure-marshallers.c : cclosure-marshallers.list @GLIB_GENMARSHAL@ --prefix $(MARSHAL_PREFIX) --body $< >$@ diff --git a/lib/gtk-experiment-widgets/gtk-experiment-navigator.c b/lib/gtk-experiment-widgets/gtk-experiment-navigator.c index fb74326..d304371 100644 --- a/lib/gtk-experiment-widgets/gtk-experiment-navigator.c +++ b/lib/gtk-experiment-widgets/gtk-experiment-navigator.c @@ -111,7 +111,7 @@ gtk_experiment_navigator_class_init(GtkExperimentNavigatorClass *klass) G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(GtkExperimentNavigatorClass, time_selected), NULL, NULL, - gtk_experiment_navigator_marshal_VOID__INT64, + gtk_experiment_widgets_marshal_VOID__INT64, G_TYPE_NONE, 1, G_TYPE_INT64); g_type_class_add_private(klass, sizeof(GtkExperimentNavigatorPrivate)); diff --git a/lib/gtk-experiment-widgets/gtk-experiment-transcript-formats.c b/lib/gtk-experiment-widgets/gtk-experiment-transcript-formats.c new file mode 100644 index 0000000..2c6c22d --- /dev/null +++ b/lib/gtk-experiment-widgets/gtk-experiment-transcript-formats.c @@ -0,0 +1,201 @@ +/** + * @file + * "Format" expression-related functions of the \e GtkExperimentTranscript + * widget + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <glib.h> +#include <glib/gprintf.h> +#include <glib/gstdio.h> + +#include <gtk/gtk.h> + +#include "gtk-experiment-transcript-private.h" + +static gboolean +gtk_experiment_transcript_parse_format(GtkExperimentTranscriptFormat *fmt, + const gchar *str) +{ + PangoAttrIterator *iter; + + gchar *pattern, pattern_captures[255], *p; + + if (!pango_parse_markup(str, -1, 0, &fmt->attribs, &pattern, + NULL, NULL)) + return TRUE; + + p = pattern_captures; + iter = pango_attr_list_get_iterator(fmt->attribs); + do { + gint start, end; + + pango_attr_iterator_range(iter, &start, &end); + + if (end < G_MAXINT) { + *p++ = '('; + strncpy(p, pattern + start, end - start); + p += end - start; + *p++ = ')'; + } + } while (pango_attr_iterator_next(iter)); + pango_attr_iterator_destroy(iter); + *p = '\0'; + g_free(pattern); + + fmt->regexp = g_regex_new(pattern_captures, G_REGEX_CASELESS, 0, NULL); + if (fmt->regexp == NULL) { + pango_attr_list_unref(fmt->attribs); + fmt->attribs = NULL; + return TRUE; + } + + return FALSE; +} + +/** @private */ +void +gtk_experiment_transcript_apply_format(GtkExperimentTranscriptFormat *fmt, + const gchar *text, + PangoAttrList *attrib_list) +{ + GMatchInfo *match_info; + + if (fmt->regexp == NULL || fmt->attribs == NULL) + return; + + g_regex_match(fmt->regexp, text, 0, &match_info); + + while (g_match_info_matches(match_info)) { + PangoAttrIterator *iter; + gint match_num = 1; + + iter = pango_attr_list_get_iterator(fmt->attribs); + do { + gint start, end; + + pango_attr_iterator_range(iter, &start, &end); + if (end == G_MAXINT) + continue; + + start = end = -1; + g_match_info_fetch_pos(match_info, match_num, + &start, &end); + if (start >= 0 && end >= 0) { + GSList *attribs; + + attribs = pango_attr_iterator_get_attrs(iter); + for (GSList *cur = attribs; cur != NULL; cur = cur->next) { + PangoAttribute *attrib; + + attrib = pango_attribute_copy((PangoAttribute *)cur->data); + attrib->start_index = (guint)start; + attrib->end_index = (guint)end; + pango_attr_list_change(attrib_list, attrib); + } + g_slist_free(attribs); + } + + match_num++; + } while (pango_attr_iterator_next(iter)); + pango_attr_iterator_destroy(iter); + + g_match_info_next(match_info, NULL); + } + + g_match_info_free(match_info); +} + +/** @private */ +void +gtk_experiment_transcript_free_formats(GSList *formats) +{ + for (GSList *cur = formats; cur != NULL; cur = cur->next) { + GtkExperimentTranscriptFormat *fmt = + (GtkExperimentTranscriptFormat *)cur->data; + + gtk_experiment_transcript_free_format(fmt); + g_free(fmt); + } + + g_slist_free(formats); +} + +/* + * API + */ + +gboolean +gtk_experiment_transcript_load_formats(GtkExperimentTranscript *trans, + const gchar *filename) +{ + FILE *file; + gchar buf[255]; + + if ((file = g_fopen(filename, "r")) == NULL) + return TRUE; + + gtk_experiment_transcript_free_formats(trans->priv->formats); + trans->priv->formats = NULL; + + while (fgets((char *)buf, sizeof(buf)-1, file) != NULL) { + GtkExperimentTranscriptFormat *fmt; + + g_strchug(buf); + + switch (*buf) { + case '#': + case '\n': + case '\0': + continue; + } + + fmt = g_new(GtkExperimentTranscriptFormat, 1); + + if (gtk_experiment_transcript_parse_format(fmt, buf)) { + g_free(fmt); + fclose(file); + return TRUE; + } + + trans->priv->formats = g_slist_prepend(trans->priv->formats, fmt); + } + trans->priv->formats = g_slist_reverse(trans->priv->formats); + + fclose(file); + + gtk_experiment_transcript_text_layer_redraw(trans); + return FALSE; +} + +gboolean +gtk_experiment_transcript_set_interactive_format(GtkExperimentTranscript *trans, + const gchar *format, + gboolean with_markup) +{ + gboolean res = TRUE; + + gtk_experiment_transcript_free_format(&trans->priv->interactive_format); + trans->priv->interactive_format.regexp = NULL; + trans->priv->interactive_format.attribs = NULL; + + if (format == NULL || *format == '\0') { + res = FALSE; + } else if (with_markup) { + res = gtk_experiment_transcript_parse_format(&trans->priv->interactive_format, + format); + } else { + /** @todo !with_markup, default attributes */ + res = TRUE; + } + + gtk_experiment_transcript_text_layer_redraw(trans); + return res; +} diff --git a/lib/gtk-experiment-widgets/gtk-experiment-transcript-private.h b/lib/gtk-experiment-widgets/gtk-experiment-transcript-private.h new file mode 100644 index 0000000..8dc47c1 --- /dev/null +++ b/lib/gtk-experiment-widgets/gtk-experiment-transcript-private.h @@ -0,0 +1,90 @@ +/** + * @file + * Private header for the \e GtkExperimentTranscript widget + */ + +#ifndef __GTK_EXPERIMENT_TRANSCRIPT_PRIVATE_H +#define __GTK_EXPERIMENT_TRANSCRIPT_PRIVATE_H + +#include "gtk-experiment-transcript.h" + +/** @private */ +#define GTK_EXPERIMENT_TRANSCRIPT_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), GTK_TYPE_EXPERIMENT_TRANSCRIPT, GtkExperimentTranscriptPrivate)) + +/** @private */ +typedef struct _GtkExperimentTranscriptFormat { + GRegex *regexp; + PangoAttrList *attribs; +} GtkExperimentTranscriptFormat; + +/** + * @private + * Private instance attribute structure. + * You can access these attributes using \c klass->priv->attribute. + */ +struct _GtkExperimentTranscriptPrivate { + GtkObject *time_adjustment; + gulong time_adj_on_value_changed_id; + + GdkPixmap *layer_text; + PangoLayout *layer_text_layout; + + GList *contribs; + GSList *formats; + GtkExperimentTranscriptFormat interactive_format; + + GtkWidget *menu; /**< Drop-down menu, doesn't have to be unreferenced manually */ +}; + +#define DEFAULT_WIDTH 100 +#define DEFAULT_HEIGHT 200 + +#define LAYER_TEXT_INVISIBLE 100 + +/** @todo scale should be configurable */ +#define PX_PER_SECOND 15 +#define TIME_TO_PX(TIME) ((TIME)/(1000/PX_PER_SECOND)) +#define PX_TO_TIME(PX) (((PX)*1000)/PX_PER_SECOND) + +/** + * @private + * Unreference object given by variable, but only once. + * Use it in \ref gtk_experiment_transcript_dispose to unreference object + * references in public or private instance attributes. + * + * @sa gtk_experiment_transcript_dispose + * + * @param VAR Variable to unreference + */ +#define GOBJECT_UNREF_SAFE(VAR) do { \ + if ((VAR) != NULL) { \ + g_object_unref(VAR); \ + VAR = NULL; \ + } \ +} while (0) + +/** @private */ +void gtk_experiment_transcript_text_layer_redraw(GtkExperimentTranscript *trans); + +/** @private */ +void gtk_experiment_transcript_apply_format(GtkExperimentTranscriptFormat *fmt, + const gchar *text, + PangoAttrList *attrib_list); + +/** @private */ +static inline void +gtk_experiment_transcript_free_format(GtkExperimentTranscriptFormat *format) +{ + if (format->regexp != NULL) + g_regex_unref(format->regexp); + if (format->attribs != NULL) + pango_attr_list_unref(format->attribs); +} + +/** @private */ +void gtk_experiment_transcript_free_formats(GSList *formats); + + + +#endif diff --git a/lib/gtk-experiment-widgets/gtk-experiment-transcript.c b/lib/gtk-experiment-widgets/gtk-experiment-transcript.c index c52b80d..e5a9384 100644 --- a/lib/gtk-experiment-widgets/gtk-experiment-transcript.c +++ b/lib/gtk-experiment-widgets/gtk-experiment-transcript.c @@ -9,7 +9,6 @@ #endif #include <assert.h> -#include <inttypes.h> #include <glib.h> #include <glib/gprintf.h> @@ -17,8 +16,7 @@ #include <gtk/gtk.h> #include <experiment-reader.h> -#include "cclosure-marshallers.h" -#include "gtk-experiment-transcript.h" +#include "gtk-experiment-transcript-private.h" static void gtk_experiment_transcript_class_init(GtkExperimentTranscriptClass *klass); static void gtk_experiment_transcript_init(GtkExperimentTranscript *klass); @@ -38,62 +36,12 @@ static void gtk_experiment_transcript_finalize(GObject *gobject); static void time_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data); -static void text_layer_redraw(GtkExperimentTranscript *trans); - static gboolean button_pressed(GtkWidget *widget, GdkEventButton *event); static void choose_font_activated(GtkWidget *widget, gpointer data); static void choose_fg_color_activated(GtkWidget *widget, gpointer data); static void choose_bg_color_activated(GtkWidget *widget, gpointer data); static void reverse_activated(GtkWidget *widget, gpointer data); -#define DEFAULT_WIDTH 100 -#define DEFAULT_HEIGHT 200 - -#define LAYER_TEXT_INVISIBLE 100 - -/** @todo scale should be configurable */ -#define PX_PER_SECOND 15 -#define TIME_TO_PX(TIME) ((TIME)/(1000/PX_PER_SECOND)) -#define PX_TO_TIME(PX) (((PX)*1000)/PX_PER_SECOND) - -/** - * @private - * Unreference object given by variable, but only once. - * Use it in \ref gtk_experiment_transcript_dispose to unreference object - * references in public or private instance attributes. - * - * @sa gtk_experiment_transcript_dispose - * - * @param VAR Variable to unreference - */ -#define GOBJECT_UNREF_SAFE(VAR) do { \ - if ((VAR) != NULL) { \ - g_object_unref(VAR); \ - VAR = NULL; \ - } \ -} while (0) - -/** @private */ -#define GTK_EXPERIMENT_TRANSCRIPT_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE((obj), GTK_TYPE_EXPERIMENT_TRANSCRIPT, GtkExperimentTranscriptPrivate)) - -/** - * @private - * Private instance attribute structure. - * You can access these attributes using \c klass->priv->attribute. - */ -struct _GtkExperimentTranscriptPrivate { - GtkObject *time_adjustment; - gulong time_adj_on_value_changed_id; - - GdkPixmap *layer_text; - PangoLayout *layer_text_layout; - - GList *contribs; - - GtkWidget *menu; /**< Drop-down menu, doesn't have to be unreferenced manually */ -}; - /** * @private * Will create \e gtk_experiment_transcript_get_type and set @@ -152,6 +100,9 @@ gtk_experiment_transcript_init(GtkExperimentTranscript *klass) pango_layout_set_ellipsize(klass->priv->layer_text_layout, PANGO_ELLIPSIZE_END); klass->priv->contribs = NULL; + klass->priv->formats = NULL; + klass->priv->interactive_format.regexp = NULL; + klass->priv->interactive_format.attribs = NULL; klass->priv->menu = gtk_menu_new(); gtk_menu_attach_to_widget(GTK_MENU(klass->priv->menu), @@ -247,6 +198,8 @@ gtk_experiment_transcript_finalize(GObject *gobject) g_free(trans->speaker); experiment_reader_free_contributions(trans->priv->contribs); + gtk_experiment_transcript_free_formats(trans->priv->formats); + gtk_experiment_transcript_free_format(&trans->priv->interactive_format); /* Chain up to the parent class */ G_OBJECT_CLASS(gtk_experiment_transcript_parent_class)->finalize(gobject); @@ -326,7 +279,7 @@ gtk_experiment_transcript_configure(GtkWidget *widget, pango_layout_set_width(trans->priv->layer_text_layout, widget->allocation.width*PANGO_SCALE); - text_layer_redraw(trans); + gtk_experiment_transcript_text_layer_redraw(trans); return TRUE; } @@ -349,16 +302,19 @@ gtk_experiment_transcript_expose(GtkWidget *widget, GdkEventExpose *event) static void time_adj_on_value_changed(GtkAdjustment *adj, gpointer user_data) { + GtkExperimentTranscript *trans = GTK_EXPERIMENT_TRANSCRIPT(user_data); + /** * @todo * heuristic to improve performance in the common case of advancing * time in small steps */ - text_layer_redraw(GTK_EXPERIMENT_TRANSCRIPT(user_data)); + gtk_experiment_transcript_text_layer_redraw(trans); } -static void -text_layer_redraw(GtkExperimentTranscript *trans) +/** @private */ +void +gtk_experiment_transcript_text_layer_redraw(GtkExperimentTranscript *trans) { GtkWidget *widget = GTK_WIDGET(trans); @@ -395,11 +351,26 @@ text_layer_redraw(GtkExperimentTranscript *trans) TIME_TO_PX(current_time - contrib->start_time); int logical_height; + PangoAttrList *attrib_list; + if (y > widget->allocation.height + LAYER_TEXT_INVISIBLE) continue; - /** @todo add attributes according to regexp masks and search mask */ - pango_layout_set_attributes(trans->priv->layer_text_layout, NULL); + attrib_list = pango_attr_list_new(); + + for (GSList *cur = trans->priv->formats; cur != NULL; cur = cur->next) { + GtkExperimentTranscriptFormat *fmt = + (GtkExperimentTranscriptFormat *)cur->data; + + gtk_experiment_transcript_apply_format(fmt, contrib->text, + attrib_list); + } + gtk_experiment_transcript_apply_format(&trans->priv->interactive_format, + contrib->text, attrib_list); + + pango_layout_set_attributes(trans->priv->layer_text_layout, + attrib_list); + pango_attr_list_unref(attrib_list); pango_layout_set_text(trans->priv->layer_text_layout, contrib->text, -1); @@ -436,12 +407,14 @@ static void choose_font_activated(GtkWidget *widget __attribute__((unused)), gpointer data) { + GtkExperimentTranscript *trans = GTK_EXPERIMENT_TRANSCRIPT(data); + GtkWidget *dialog; gchar *font_name; dialog = gtk_font_selection_dialog_new("Choose Font..."); - font_name = pango_font_description_to_string(GTK_WIDGET(data)->style->font_desc); + font_name = pango_font_description_to_string(GTK_WIDGET(trans)->style->font_desc); gtk_font_selection_dialog_set_font_name(GTK_FONT_SELECTION_DIALOG(dialog), font_name); g_free(font_name); @@ -452,12 +425,12 @@ choose_font_activated(GtkWidget *widget __attribute__((unused)), font_name = gtk_font_selection_dialog_get_font_name(GTK_FONT_SELECTION_DIALOG(dialog)); font_desc = pango_font_description_from_string(font_name); - gtk_widget_modify_font(GTK_WIDGET(data), font_desc); + gtk_widget_modify_font(GTK_WIDGET(trans), font_desc); pango_font_description_free(font_desc); g_free(font_name); - text_layer_redraw(GTK_EXPERIMENT_TRANSCRIPT(data)); + gtk_experiment_transcript_text_layer_redraw(trans); } gtk_widget_destroy(dialog); @@ -467,22 +440,25 @@ static void choose_fg_color_activated(GtkWidget *widget __attribute__((unused)), gpointer data) { + GtkExperimentTranscript *trans = GTK_EXPERIMENT_TRANSCRIPT(data); + GtkWidget *dialog, *colorsel; dialog = gtk_color_selection_dialog_new("Choose Foreground Color..."); colorsel = gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dialog)); gtk_color_selection_set_current_color(GTK_COLOR_SELECTION(colorsel), - >K_WIDGET(data)->style->text[GTK_STATE_NORMAL]); + >K_WIDGET(trans)->style->text[GTK_STATE_NORMAL]); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { GdkColor color; gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(colorsel), &color); - gtk_widget_modify_text(GTK_WIDGET(data), GTK_STATE_NORMAL, &color); + gtk_widget_modify_text(GTK_WIDGET(trans), + GTK_STATE_NORMAL, &color); - text_layer_redraw(GTK_EXPERIMENT_TRANSCRIPT(data)); + gtk_experiment_transcript_text_layer_redraw(trans); } gtk_widget_destroy(dialog); @@ -492,22 +468,25 @@ static void choose_bg_color_activated(GtkWidget *widget __attribute__((unused)), gpointer data) { + GtkExperimentTranscript *trans = GTK_EXPERIMENT_TRANSCRIPT(data); + GtkWidget *dialog, *colorsel; dialog = gtk_color_selection_dialog_new("Choose Background Color..."); colorsel = gtk_color_selection_dialog_get_color_selection(GTK_COLOR_SELECTION_DIALOG(dialog)); gtk_color_selection_set_current_color(GTK_COLOR_SELECTION(colorsel), - >K_WIDGET(data)->style->bg[GTK_STATE_NORMAL]); + >K_WIDGET(trans)->style->bg[GTK_STATE_NORMAL]); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { GdkColor color; gtk_color_selection_get_current_color(GTK_COLOR_SELECTION(colorsel), &color); - gtk_widget_modify_bg(GTK_WIDGET(data), GTK_STATE_NORMAL, &color); + gtk_widget_modify_bg(GTK_WIDGET(trans), + GTK_STATE_NORMAL, &color); - text_layer_redraw(GTK_EXPERIMENT_TRANSCRIPT(data)); + gtk_experiment_transcript_text_layer_redraw(trans); } gtk_widget_destroy(dialog); @@ -555,7 +534,7 @@ gtk_experiment_transcript_load(GtkExperimentTranscript *trans, trans->priv->contribs = experiment_reader_get_contributions_by_speaker(exp, trans->speaker); - text_layer_redraw(trans); + gtk_experiment_transcript_text_layer_redraw(trans); return trans->priv->contribs == NULL; } diff --git a/lib/gtk-experiment-widgets/gtk-experiment-transcript.h b/lib/gtk-experiment-widgets/gtk-experiment-transcript.h index 3b34754..a242a57 100644 --- a/lib/gtk-experiment-widgets/gtk-experiment-transcript.h +++ b/lib/gtk-experiment-widgets/gtk-experiment-transcript.h @@ -68,6 +68,12 @@ gboolean gtk_experiment_transcript_load(GtkExperimentTranscript *trans, gboolean gtk_experiment_transcript_load_filename(GtkExperimentTranscript *trans, const gchar *filename); +gboolean gtk_experiment_transcript_load_formats(GtkExperimentTranscript *trans, + const gchar *filename); +gboolean gtk_experiment_transcript_set_interactive_format(GtkExperimentTranscript *trans, + const gchar *format, + gboolean with_markup); + GtkAdjustment *gtk_experiment_transcript_get_time_adjustment(GtkExperimentTranscript *trans); void gtk_experiment_transcript_set_time_adjustment(GtkExperimentTranscript *trans, GtkAdjustment *adj); |