/* * GdiffRange widget module * See "gdiffrange.h" about details. * * Copyright INOUE Seiichiro , licensed under the GPL. */ #include #include "gdiffrange.h" /* Constant number */ enum { ARG_0, ARG_ADJUSTMENT }; #define MIN_SLIDER_LENGTH 20 /* Private function declarations */ static void gdiff_range_class_init(GdiffRangeClass *klass); static void gdiff_range_set_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_range_get_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_range_init(GdiffRange *range); static void gdiff_range_finalize(GtkObject *object); static void gdiff_range_realize(GtkWidget *widget); static void gdiff_range_unrealize(GtkWidget *widget); static void gdiff_range_size_allocate(GtkWidget *widget, GtkAllocation *allocation); static gint gdiff_range_expose(GtkWidget *widget, GdkEventExpose *event); static void gdiff_range_adjustment_changed(GtkAdjustment *adjustment, gpointer data); static void gdiff_range_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data); static void gdiff_range_draw_slider(GdiffRange *range, const GdkRectangle *area); static void gdiff_range_clear_background(GtkWidget *widget, const GdkRectangle *area); static void gdiff_range_draw_ranges(GdiffRange *range, const GdkRectangle *area); static GtkWidgetClass *parent_class = NULL; GtkType gdiff_range_get_type(void) { static GtkType range_type = 0; if (!range_type) { static const GtkTypeInfo range_info = { "GdiffRange", sizeof(GdiffRange), sizeof(GdiffRangeClass), (GtkClassInitFunc)gdiff_range_class_init, (GtkObjectInitFunc)gdiff_range_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; range_type = gtk_type_unique(GTK_TYPE_WIDGET, &range_info); } return range_type; } static void gdiff_range_class_init(GdiffRangeClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; gtk_object_add_arg_type("GdiffRange::adjustment", GTK_TYPE_ADJUSTMENT, GTK_ARG_READWRITE | GTK_ARG_CONSTRUCT, ARG_ADJUSTMENT); object_class = (GtkObjectClass*)klass; widget_class = (GtkWidgetClass*)klass; parent_class = gtk_type_class(GTK_TYPE_WIDGET); object_class->set_arg = gdiff_range_set_arg; object_class->get_arg = gdiff_range_get_arg; object_class->finalize = gdiff_range_finalize; widget_class->realize = gdiff_range_realize; widget_class->unrealize = gdiff_range_unrealize; widget_class->size_allocate = gdiff_range_size_allocate; widget_class->expose_event = gdiff_range_expose; } static void gdiff_range_set_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffRange *range; range = GDIFF_RANGE(object); switch (arg_id) { case ARG_ADJUSTMENT: gdiff_range_set_adjustment(range, GTK_VALUE_POINTER(*arg)); break; default: break; } } static void gdiff_range_get_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffRange *range; range = GDIFF_RANGE(object); switch (arg_id) { case ARG_ADJUSTMENT: GTK_VALUE_POINTER(*arg) = range->adjustment; break; default: arg->type = GTK_TYPE_INVALID; break; } } static void gdiff_range_init(GdiffRange *range) { range->slider_y = 0; range->slider_length = MIN_SLIDER_LENGTH; range->adjustment = NULL; range->old_value = 0.0; range->old_lower = 0.0; range->old_upper = 0.0; range->old_page_size = 0.0; range->xor_gc = NULL; range->range_gc = NULL; range->range_fg = NULL; range->range_bg = NULL; range->range_list = NULL; } static void gdiff_range_finalize(GtkObject *object) { GSList *list; GdiffRange *range; range = GDIFF_RANGE(object); if (range->adjustment) { gtk_signal_disconnect_by_data(GTK_OBJECT(range->adjustment), (gpointer)range); gtk_object_unref(GTK_OBJECT(range->adjustment)); } if (range->range_fg) gdk_color_free(range->range_fg); if (range->range_bg) gdk_color_free(range->range_bg); for (list = range->range_list; list; list = list->next) { g_free(list->data); } g_slist_free(range->range_list); (*GTK_OBJECT_CLASS(parent_class)->finalize)(object); } /** * gdiff_range_new: **/ GtkWidget* gdiff_range_new(GtkAdjustment *adjustment) { GtkWidget *range; range = gtk_widget_new(GDIFF_TYPE_RANGE, "adjustment", adjustment, NULL); return range; } /** Interfaces **/ /** * gdiff_range_size: * Set size of the widget. **/ void gdiff_range_size(GdiffRange *range, gint width, gint height) { g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); GTK_WIDGET(range)->requisition.width = width; GTK_WIDGET(range)->requisition.height = height; } /** * gdiff_range_set_adjustment: **/ void gdiff_range_set_adjustment(GdiffRange *range, GtkAdjustment *adjustment) { g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); if (!adjustment) adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); else g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment)); if (range->adjustment != adjustment) { if (range->adjustment) { gtk_signal_disconnect_by_data(GTK_OBJECT(range->adjustment), (gpointer)range); gtk_object_unref(GTK_OBJECT(range->adjustment)); } range->adjustment = adjustment; gtk_object_ref(GTK_OBJECT(adjustment)); gtk_object_sink(GTK_OBJECT(adjustment)); gtk_signal_connect(GTK_OBJECT(adjustment), "changed", GTK_SIGNAL_FUNC(gdiff_range_adjustment_changed), (gpointer)range); gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", GTK_SIGNAL_FUNC(gdiff_range_adjustment_value_changed), (gpointer)range); range->old_value = adjustment->value; range->old_lower = adjustment->lower; range->old_upper = adjustment->upper; range->old_page_size = adjustment->page_size; gdiff_range_adjustment_changed(adjustment, (gpointer)range); } } /** * gdiff_range_insert_paintrange: **/ void gdiff_range_insert_paintrange(GdiffRange *range, gdouble begin, gdouble end) { PaintRange *prange; g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); g_return_if_fail(begin >= 0); g_return_if_fail(end >= 0); /* begin > 1 or end > 1 can happen, when the both last one line is different. I'm not sure I can get around this by a better logic. */ if (begin < 0) begin = 0; if (begin > 1) begin = 1; if (end < 0) end = 0; if (end > 1) end = 1; prange = g_new(PaintRange, 1); prange->begin = begin; prange->end = end; range->range_list = g_slist_prepend(range->range_list, prange); } /** * gdiff_range_set_foreground: **/ void gdiff_range_set_foreground(GdiffRange *range, GdkColor *color) { g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); g_return_if_fail(color != NULL); if (range->range_fg) gdk_color_free(range->range_fg); range->range_fg = gdk_color_copy(color); } /** * gdiff_range_set_background: **/ void gdiff_range_set_background(GdiffRange *range, GdkColor *color) { g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); g_return_if_fail(color != NULL); if (range->range_bg) gdk_color_free(range->range_bg); range->range_bg = gdk_color_copy(color); } /** Internal functions **/ static void gdiff_range_realize(GtkWidget *widget) { GdiffRange *range; GdkWindowAttr attributes; gint attributes_mask; g_return_if_fail(widget != NULL); g_return_if_fail(GDIFF_IS_RANGE(widget)); range = GDIFF_RANGE(widget); GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual(widget); attributes.colormap = gtk_widget_get_colormap(widget); attributes.event_mask = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask); gdk_window_set_user_data(widget->window, range); widget->style = gtk_style_attach(widget->style, widget->window); gtk_style_set_background(widget->style, widget->window, GTK_STATE_NORMAL); range->range_gc = gdk_gc_new(widget->window); if (range->range_fg) gdk_gc_set_foreground(range->range_gc, range->range_fg); if (range->range_bg) gdk_gc_set_background(range->range_gc, range->range_bg); /* Use color of fg_gc, which a user can specify in gtkdiffrc file. */ range->xor_gc = gdk_gc_new(widget->window); gdk_gc_copy(range->xor_gc, widget->style->fg_gc[GTK_STATE_NORMAL]); gdk_gc_set_function(range->xor_gc, GDK_XOR); } static void gdiff_range_unrealize(GtkWidget *widget) { GdiffRange *range; range = GDIFF_RANGE(widget); if (range->range_fg) { gdk_gc_unref(range->range_gc); range->range_gc = NULL; } if (range->xor_gc) { gdk_gc_unref(range->xor_gc); range->xor_gc = NULL; } if (GTK_WIDGET_CLASS(parent_class)->unrealize) (*GTK_WIDGET_CLASS(parent_class)->unrealize)(widget); } static void gdiff_range_size_allocate(GtkWidget *widget, GtkAllocation *allocation) { g_return_if_fail(widget != NULL); g_return_if_fail(GDIFF_IS_RANGE(widget)); g_return_if_fail(allocation != NULL); widget->allocation = *allocation; if (GTK_WIDGET_REALIZED(widget)) { gdk_window_move_resize(widget->window, allocation->x, allocation->y, allocation->width, allocation->height); } } static gint gdiff_range_expose(GtkWidget *widget, GdkEventExpose *event) { gdiff_range_clear_background(widget, &event->area); gdiff_range_draw_ranges(GDIFF_RANGE(widget), &event->area); gdiff_range_draw_slider(GDIFF_RANGE(widget), &event->area); return TRUE; } static void gdiff_range_adjustment_changed(GtkAdjustment *adjustment, gpointer data) { GdiffRange *range; g_return_if_fail(adjustment != NULL); g_return_if_fail(data != NULL); range = GDIFF_RANGE(data); if (((range->old_lower != adjustment->lower) || (range->old_upper != adjustment->upper) || (range->old_page_size != adjustment->page_size)) && (range->old_value == adjustment->value)) { if ((adjustment->lower == adjustment->upper) || (range->old_lower == (range->old_upper - range->old_page_size))) { adjustment->value = adjustment->lower; gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); } } if ((range->old_value != adjustment->value) || (range->old_lower != adjustment->lower) || (range->old_upper != adjustment->upper) || (range->old_page_size != adjustment->page_size)) { range->old_value = adjustment->value; range->old_lower = adjustment->lower; range->old_upper = adjustment->upper; range->old_page_size = adjustment->page_size; } } static void gdiff_range_adjustment_value_changed(GtkAdjustment *adjustment, gpointer data) { GdiffRange *range; g_return_if_fail(adjustment != NULL); g_return_if_fail(data != NULL); range = GDIFF_RANGE(data); if (range->old_value != adjustment->value) { GdkRectangle rect; gint w, h; gdk_window_get_size(GTK_WIDGET(range)->window, &w, &h); rect.x = rect.y = 0; rect.width = w; rect.height = h; /* redraw all areas */ gdiff_range_clear_background(GTK_WIDGET(range), &rect); gdiff_range_draw_ranges(range, &rect); gdiff_range_draw_slider(range, &rect); range->old_value = adjustment->value; } } static void gdiff_range_draw_slider(GdiffRange *range, const GdkRectangle *area) { gint width; gint height; gint top; gint bottom; GdkRectangle s_rect;/* slider rectangle */ GdkRectangle i_rect;/* intersect between s_rect and the area to draw */ GtkWidget *widget = GTK_WIDGET(range); g_return_if_fail(range != NULL); g_return_if_fail(GDIFF_IS_RANGE(range)); g_return_if_fail(GTK_WIDGET(range)->window != NULL); gdk_window_get_size(GTK_WIDGET(range)->window, &width, &height); /* default values */ s_rect.x = 0; s_rect.y = range->slider_y; s_rect.width = width; s_rect.height = range->slider_length; /*XXX: calc slider height(length) */ if ((range->adjustment->page_size > 0) && (range->adjustment->lower != range->adjustment->upper)) { if (range->adjustment->page_size > (range->adjustment->upper - range->adjustment->lower)) range->adjustment->page_size = range->adjustment->upper - range->adjustment->lower; s_rect.height = (height * range->adjustment->page_size / (range->adjustment->upper - range->adjustment->lower)); if (s_rect.height < MIN_SLIDER_LENGTH) s_rect.height = MIN_SLIDER_LENGTH; } /* calc slider pos y */ top = 0; bottom = height - range->slider_length; if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size)) { s_rect.y = ((bottom - top) * (range->adjustment->value - range->adjustment->lower) / (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); } /* adjustment for special cases */ if (s_rect.y < top) s_rect.y = top; else if (s_rect.y > bottom) s_rect.y = bottom; if (gdk_rectangle_intersect((GdkRectangle*)area, &s_rect, &i_rect)) { gdk_draw_rectangle(widget->window, range->xor_gc, 1, i_rect.x, i_rect.y, i_rect.width, i_rect.height); } range->slider_y = s_rect.y; range->slider_length = s_rect.height; } static void gdiff_range_clear_background(GtkWidget *widget, const GdkRectangle *area) { g_return_if_fail(widget->window != NULL); gdk_window_clear_area (widget->window, area->x, area->y, area->width, area->height); } /** * gdiff_range_draw_ranges: * Draw (diff)ranges, i.e. rectagles related to each differences. * Input: * GdiffRange *range; * const GdkRectangle *area; The area to draw, (usually)the exposed area. **/ static void gdiff_range_draw_ranges(GdiffRange *range, const GdkRectangle *area) { GSList *node; GdkRectangle r_rect;/* range rectangle */ GdkRectangle i_rect;/* intersect between r_rect and the area to draw */ gint w, h; gdk_window_get_size(GTK_WIDGET(range)->window, &w, &h); r_rect.x = 0; r_rect.width = w; for (node = range->range_list; node; node = node->next) { const PaintRange *prange = node->data; r_rect.y = h * prange->begin; r_rect.height = h * (prange->end - prange->begin); /* adjustment for special cases */ if (r_rect.height < 1) r_rect.height = 1; if (r_rect.y == h) r_rect.y--; if (gdk_rectangle_intersect((GdkRectangle*)area, &r_rect, &i_rect)) { gdk_draw_rectangle(GTK_WIDGET(range)->window, range->range_gc, 1, i_rect.x, i_rect.y, i_rect.width, i_rect.height); } } }