<commit_before>// +build !windows,!darwin,!plan9

// 14 march 2014

package ui

import (
	"fmt"
	"image"
	"unsafe"
)

// #include "gtk_unix.h"
// extern gboolean our_area_draw_callback(GtkWidget *, cairo_t *, gpointer);
// extern gboolean our_area_button_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_button_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_motion_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_enterleave_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// /* because cgo doesn't like ... */
// static inline void gtkGetDoubleClickSettings(GtkSettings *settings, gint *maxTime, gint *maxDistance)
// {
// 	g_object_get(settings,
// 		"gtk-double-click-time", maxTime,
// 		"gtk-double-click-distance", maxDistance,
// 		NULL);
// }
import "C"

type area struct {
	*areabase

	_widget		*C.GtkWidget
	drawingarea	*C.GtkDrawingArea
	scroller		*scroller

	clickCounter	*clickCounter
}

func newArea(ab *areabase) Area {
	widget := C.gtk_drawing_area_new()
	// the Area's size will be set later
	// we need to explicitly subscribe to mouse events with GtkDrawingArea
	C.gtk_widget_add_events(widget,
		C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK|C.GDK_POINTER_MOTION_MASK|C.GDK_BUTTON_MOTION_MASK|C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK)
	// and we need to allow focusing on a GtkDrawingArea to enable keyboard events
	C.gtk_widget_set_can_focus(widget, C.TRUE)
	a := &area{
		areabase:		ab,
		_widget:		widget,
		drawingarea:	(*C.GtkDrawingArea)(unsafe.Pointer(widget)),
		scroller:		newScroller(widget, false, false),		// not natively scrollable, no border
		clickCounter:	new(clickCounter),
	}
	for _, c := range areaCallbacks {
		g_signal_connect(
			C.gpointer(unsafe.Pointer(a.drawingarea)),
			c.name,
			c.callback,
			C.gpointer(unsafe.Pointer(a)))
	}
	a.SetSize(a.width, a.height)
	return a
}

func (a *area) SetSize(width, height int) {
	a.width = width
	a.height = height
	C.gtk_widget_set_size_request(a._widget, C.gint(a.width), C.gint(a.height))
}

func (a *area) Repaint(r image.Rectangle) {
	r = image.Rect(0, 0, a.width, a.height).Intersect(r)
	if r.Empty() {
		return
	}
println(a._widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy()))
	C.gtk_widget_queue_draw_area(a._widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy()))
}

func (a *area) RepaintAll() {
	C.gtk_widget_queue_draw(a._widget)
}

var areaCallbacks = []struct {
	name	string
	callback	C.GCallback
}{
	{ "draw",					area_draw_callback },
	{ "button-press-event",		area_button_press_event_callback },
	{ "button-release-event",		area_button_release_event_callback },
	{ "motion-notify-event",		area_motion_notify_event_callback },
	{ "enter-notify-event",		area_enterleave_notify_event_callback },
	{ "leave-notify-event",		area_enterleave_notify_event_callback },
	{ "key-press-event",			area_key_press_event_callback },
	{ "key-release-event",		area_key_release_event_callback },
}

//export our_area_draw_callback
func our_area_draw_callback(widget *C.GtkWidget, cr *C.cairo_t, data C.gpointer) C.gboolean {
	var x0, y0, x1, y1 C.double

	a := (*area)(unsafe.Pointer(data))
	// thanks to desrt in irc.gimp.net/#gtk+
	// these are in user coordinates, which match what coordinates we want by default, even out of a draw event handler (thanks johncc3, mclasen, and Company in irc.gimp.net/#gtk+)
	C.cairo_clip_extents(cr, &x0, &y0, &x1, &y1)
	// we do not need to clear the cliprect; GtkDrawingArea did it for us beforehand
	cliprect := image.Rect(int(x0), int(y0), int(x1), int(y1))
	// the cliprect can actually fall outside the size of the Area; clip it by intersecting the two rectangles
	cliprect = image.Rect(0, 0, a.width, a.height).Intersect(cliprect)
	if cliprect.Empty() { // no intersection; nothing to paint
		return C.FALSE // signals handled without stopping the event chain (thanks to desrt again)
	}
	i := a.handler.Paint(cliprect)
	surface := C.cairo_image_surface_create(
		C.CAIRO_FORMAT_ARGB32, // alpha-premultiplied; native byte order
		C.int(i.Rect.Dx()),
		C.int(i.Rect.Dy()))
	if status := C.cairo_surface_status(surface); status != C.CAIRO_STATUS_SUCCESS {
		panic(fmt.Errorf("cairo_create_image_surface() failed: %s\n",
			C.GoString(C.cairo_status_to_string(status))))
	}
	// the flush and mark_dirty calls are required; see the cairo docs and https://git.gnome.org/browse/gtk+/tree/gdk/gdkcairo.c#n232 (thanks desrt in irc.gimp.net/#gtk+)
	C.cairo_surface_flush(surface)
	toARGB(i, uintptr(unsafe.Pointer(C.cairo_image_surface_get_data(surface))),
		int(C.cairo_image_surface_get_stride(surface)))
	C.cairo_surface_mark_dirty(surface)
	C.cairo_set_source_surface(cr,
		surface,
		0, 0) // origin of the surface
	// that just set the brush that cairo uses: we have to actually draw now
	// (via https://developer.gnome.org/gtkmm-tutorial/stable/sec-draw-images.html.en)
	C.cairo_rectangle(cr, x0, y0, x1, y1) // breaking the norm here since we have the coordinates as a C double already
	C.cairo_fill(cr)
	C.cairo_surface_destroy(surface) // free surface
	return C.FALSE                   // signals handled without stopping the event chain (thanks to desrt again)
}<commit_msg>Fixed Area.Repaint() bugs on GTK+.<commit_after>// +build !windows,!darwin,!plan9

// 14 march 2014

package ui

import (
	"fmt"
	"image"
	"unsafe"
)

// #include "gtk_unix.h"
// extern gboolean our_area_draw_callback(GtkWidget *, cairo_t *, gpointer);
// extern gboolean our_area_button_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_button_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_motion_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_enterleave_notify_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_press_event_callback(GtkWidget *, GdkEvent *, gpointer);
// extern gboolean our_area_key_release_event_callback(GtkWidget *, GdkEvent *, gpointer);
// /* because cgo doesn't like ... */
// static inline void gtkGetDoubleClickSettings(GtkSettings *settings, gint *maxTime, gint *maxDistance)
// {
// 	g_object_get(settings,
// 		"gtk-double-click-time", maxTime,
// 		"gtk-double-click-distance", maxDistance,
// 		NULL);
// }
import "C"

type area struct {
	*areabase

	_widget		*C.GtkWidget
	drawingarea	*C.GtkDrawingArea
	scroller		*scroller

	clickCounter	*clickCounter
}

func newArea(ab *areabase) Area {
	widget := C.gtk_drawing_area_new()
	// the Area's size will be set later
	// we need to explicitly subscribe to mouse events with GtkDrawingArea
	C.gtk_widget_add_events(widget,
		C.GDK_BUTTON_PRESS_MASK|C.GDK_BUTTON_RELEASE_MASK|C.GDK_POINTER_MOTION_MASK|C.GDK_BUTTON_MOTION_MASK|C.GDK_ENTER_NOTIFY_MASK|C.GDK_LEAVE_NOTIFY_MASK)
	// and we need to allow focusing on a GtkDrawingArea to enable keyboard events
	C.gtk_widget_set_can_focus(widget, C.TRUE)
	a := &area{
		areabase:		ab,
		_widget:		widget,
		drawingarea:	(*C.GtkDrawingArea)(unsafe.Pointer(widget)),
		scroller:		newScroller(widget, false, false),		// not natively scrollable, no border
		clickCounter:	new(clickCounter),
	}
	for _, c := range areaCallbacks {
		g_signal_connect(
			C.gpointer(unsafe.Pointer(a.drawingarea)),
			c.name,
			c.callback,
			C.gpointer(unsafe.Pointer(a)))
	}
	a.SetSize(a.width, a.height)
	return a
}

func (a *area) SetSize(width, height int) {
	a.width = width
	a.height = height
	C.gtk_widget_set_size_request(a._widget, C.gint(a.width), C.gint(a.height))
}

func (a *area) Repaint(r image.Rectangle) {
	r = image.Rect(0, 0, a.width, a.height).Intersect(r)
	if r.Empty() {
		return
	}
println(a._widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy()))
	C.gtk_widget_queue_draw_area(a._widget, C.gint(r.Min.X), C.gint(r.Min.Y), C.gint(r.Dx()), C.gint(r.Dy()))
}

func (a *area) RepaintAll() {
	C.gtk_widget_queue_draw(a._widget)
}

var areaCallbacks = []struct {
	name	string
	callback	C.GCallback
}{
	{ "draw",					area_draw_callback },
	{ "button-press-event",		area_button_press_event_callback },
	{ "button-release-event",		area_button_release_event_callback },
	{ "motion-notify-event",		area_motion_notify_event_callback },
	{ "enter-notify-event",		area_enterleave_notify_event_callback },
	{ "leave-notify-event",		area_enterleave_notify_event_callback },
	{ "key-press-event",			area_key_press_event_callback },
	{ "key-release-event",		area_key_release_event_callback },
}

//export our_area_draw_callback
func our_area_draw_callback(widget *C.GtkWidget, cr *C.cairo_t, data C.gpointer) C.gboolean {
	var x0, y0, x1, y1 C.double

	a := (*area)(unsafe.Pointer(data))
	// thanks to desrt in irc.gimp.net/#gtk+
	// these are in user coordinates, which match what coordinates we want by default, even out of a draw event handler (thanks johncc3, mclasen, and Company in irc.gimp.net/#gtk+)
	C.cairo_clip_extents(cr, &x0, &y0, &x1, &y1)
	// we do not need to clear the cliprect; GtkDrawingArea did it for us beforehand
	cliprect := image.Rect(int(x0), int(y0), int(x1), int(y1))
	// the cliprect can actually fall outside the size of the Area; clip it by intersecting the two rectangles
	cliprect = image.Rect(0, 0, a.width, a.height).Intersect(cliprect)
	if cliprect.Empty() { // no intersection; nothing to paint
		return C.FALSE // signals handled without stopping the event chain (thanks to desrt again)
	}
	i := a.handler.Paint(cliprect)
	surface := C.cairo_image_surface_create(
		C.CAIRO_FORMAT_ARGB32, // alpha-premultiplied; native byte order
		C.int(i.Rect.Dx()),
		C.int(i.Rect.Dy()))
	if status := C.cairo_surface_status(surface); status != C.CAIRO_STATUS_SUCCESS {
		panic(fmt.Errorf("cairo_create_image_surface() failed: %s\n",
			C.GoString(C.cairo_status_to_string(status))))
	}
	// the flush and mark_dirty calls are required; see the cairo docs and https://git.gnome.org/browse/gtk+/tree/gdk/gdkcairo.c#n232 (thanks desrt in irc.gimp.net/#gtk+)
	C.cairo_surface_flush(surface)
	toARGB(i, uintptr(unsafe.Pointer(C.cairo_image_surface_get_data(surface))),
		int(C.cairo_image_surface_get_stride(surface)))
	C.cairo_surface_mark_dirty(surface)
	C.cairo_set_source_surface(cr,
		surface,
		x0, y0)			// point on cairo_t where we want to draw (thanks Company in irc.gimp.net/#gtk+)
	// that just set the brush that cairo uses: we have to actually draw now
	// (via https://developer.gnome.org/gtkmm-tutorial/stable/sec-draw-images.html.en)
	C.cairo_rectangle(cr, x0, y0, x1, y1) // breaking the norm here since we have the coordinates as a C double already
	C.cairo_fill(cr)
	C.cairo_surface_destroy(surface) // free surface
	return C.FALSE                   // signals handled without stopping the event chain (thanks to desrt again)
}
{'fraction_non_alphanumeric': 0.12982998454404945, 'fraction_numerical': 0.006364214928629875, 'mean_word_length': 4.723204994797086, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '760959', 'n_tokens_mistral': 4267, 'n_tokens_neox': 4082, 'n_words': 886}