From 344a344abd471e36ebd6ea0ced71df1ce74ea189 Mon Sep 17 00:00:00 2001 From: Pietro Gagliardi Date: Fri, 17 Oct 2014 17:01:24 -0400 Subject: [PATCH] Began the GTK+ backend migration. control_unix.go is migrated; Area overlay changes are done now so I don't forget later as well. --- newctrl/area_unix.go | 519 ++++++++++++++++++++++++++++++++++++++++ newctrl/control_unix.go | 121 ++++++++++ 2 files changed, 640 insertions(+) create mode 100644 newctrl/area_unix.go create mode 100644 newctrl/control_unix.go diff --git a/newctrl/area_unix.go b/newctrl/area_unix.go new file mode 100644 index 0000000..01e604c --- /dev/null +++ b/newctrl/area_unix.go @@ -0,0 +1,519 @@ +// +build !windows,!darwin,!plan9 + +// 14 march 2014 + +package ui + +import ( + "fmt" + "image" + "unsafe" +) + +// #include "gtk_unix.h" +// extern gboolean our_area_get_child_position_callback(GtkOverlay *, GtkWidget *, GdkRectangle *, gpointer); +// extern void our_area_textfield_populate_popup_callback(GtkEntry *, GtkMenu *, gpointer); +// extern gboolean our_area_textfield_focus_out_event_callback(GtkWidget *, GdkEvent *, gpointer); +// 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 + + textfieldw *C.GtkWidget + textfield *C.GtkEntry + textfieldx int + textfieldy int + textfielddone *event + inmenu bool +} + +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) + textfieldw := C.gtk_entry_new() + a := &area{ + areabase: ab, + _widget: widget, + drawingarea: (*C.GtkDrawingArea)(unsafe.Pointer(widget)), + scroller: newScroller(widget, false, false, true), // not natively scrollable; no border; have an overlay for OpenTextFieldAt() + clickCounter: new(clickCounter), + textfieldw: textfieldw, + textfield: (*C.GtkEntry)(unsafe.Pointer(textfieldw)), + textfielddone: newEvent(), + } + 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) + C.gtk_overlay_add_overlay(a.scroller.overlayoverlay, a.textfieldw) + g_signal_connect( + C.gpointer(unsafe.Pointer(a.scroller.overlayoverlay)), + "get-child-position", + area_get_child_position_callback, + C.gpointer(unsafe.Pointer(a))) + // this part is important + // entering the context menu is considered focusing out + // so we connect to populate-popup to mark that we're entering the context menu (thanks slaf in irc.gimp.net/#gtk+) + // and we have to connect_after to focus-out-event so that it runs after the populate-popup + g_signal_connect( + C.gpointer(unsafe.Pointer(a.textfield)), + "populate-popup", + area_textfield_populate_popup_callback, + C.gpointer(unsafe.Pointer(a))) + g_signal_connect_after( + C.gpointer(unsafe.Pointer(a.textfield)), + "focus-out-event", + area_textfield_focus_out_event_callback, + C.gpointer(unsafe.Pointer(a))) + // the widget shows up initially + C.gtk_widget_set_no_show_all(a.textfieldw, C.TRUE) + 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 + } + 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) +} + +func (a *area) OpenTextFieldAt(x, y int) { + if x < 0 || x >= a.width || y < 0 || y >= a.height { + panic(fmt.Errorf("point (%d,%d) outside Area in Area.OpenTextFieldAt()", x, y)) + } + a.textfieldx = x + a.textfieldy = y + a.inmenu = false // to start + // we disabled this for the initial Area show; we don't need to anymore + C.gtk_widget_set_no_show_all(a.textfieldw, C.FALSE) + C.gtk_widget_show_all(a.textfieldw) + C.gtk_widget_grab_focus(a.textfieldw) +} + +func (a *area) TextFieldText() string { + return fromgstr(C.gtk_entry_get_text(a.textfield)) +} + +func (a *area) SetTextFieldText(text string) { + ctext := togstr(text) + defer freegstr(ctext) + C.gtk_entry_set_text(a.textfield, ctext) +} + +func (a *area) OnTextFieldDismissed(f func()) { + a.textfielddone.set(f) +} + +//export our_area_get_child_position_callback +func our_area_get_child_position_callback(overlay *C.GtkOverlay, widget *C.GtkWidget, rect *C.GdkRectangle, data C.gpointer) C.gboolean { + var nat C.GtkRequisition + + a := (*area)(unsafe.Pointer(data)) + rect.x = C.int(a.textfieldx) + rect.y = C.int(a.textfieldy) + C.gtk_widget_get_preferred_size(a.textfieldw, nil, &nat) + rect.width = C.int(nat.width) + rect.height = C.int(nat.height) + return C.TRUE +} + +var area_get_child_position_callback = C.GCallback(C.our_area_get_child_position_callback) + +//export our_area_textfield_populate_popup_callback +func our_area_textfield_populate_popup_callback(entry *C.GtkEntry, menu *C.GtkMenu, data C.gpointer) { + a := (*area)(unsafe.Pointer(data)) + a.inmenu = true +} + +var area_textfield_populate_popup_callback = C.GCallback(C.our_area_textfield_populate_popup_callback) + +//export our_area_textfield_focus_out_event_callback +func our_area_textfield_focus_out_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + a := (*area)(unsafe.Pointer(data)) + if !a.inmenu { + C.gtk_widget_hide(a.textfieldw) + a.textfielddone.fire() + } + a.inmenu = false // for next time + return continueEventChain +} + +var area_textfield_focus_out_event_callback = C.GCallback(C.our_area_textfield_focus_out_event_callback) + +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)), false) // not NRGBA + 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) +} + +var area_draw_callback = C.GCallback(C.our_area_draw_callback) + +func translateModifiers(state C.guint, window *C.GdkWindow) C.guint { + // GDK doesn't initialize the modifier flags fully; we have to explicitly tell it to (thanks to Daniel_S and daniels (two different people) in irc.gimp.net/#gtk+) + C.gdk_keymap_add_virtual_modifiers( + C.gdk_keymap_get_for_display(C.gdk_window_get_display(window)), + (*C.GdkModifierType)(unsafe.Pointer(&state))) + return state +} + +func makeModifiers(state C.guint) (m Modifiers) { + if (state & C.GDK_CONTROL_MASK) != 0 { + m |= Ctrl + } + if (state & C.GDK_META_MASK) != 0 { + m |= Alt + } + if (state & C.GDK_MOD1_MASK) != 0 { // GTK+ itself requires this to be Alt (just read through gtkaccelgroup.c) + m |= Alt + } + if (state & C.GDK_SHIFT_MASK) != 0 { + m |= Shift + } + if (state & C.GDK_SUPER_MASK) != 0 { + m |= Super + } + return m +} + +// shared code for finishing up and sending a mouse event +func finishMouseEvent(widget *C.GtkWidget, data C.gpointer, me MouseEvent, mb uint, x C.gdouble, y C.gdouble, state C.guint, gdkwindow *C.GdkWindow) { + var areawidth, areaheight C.gint + + // on GTK+, mouse buttons 4-7 are for scrolling; if we got here, that's a mistake + if mb >= 4 && mb <= 7 { + return + } + a := (*area)(unsafe.Pointer(data)) + state = translateModifiers(state, gdkwindow) + me.Modifiers = makeModifiers(state) + // the mb != # checks exclude the Up/Down button from Held + if mb != 1 && (state&C.GDK_BUTTON1_MASK) != 0 { + me.Held = append(me.Held, 1) + } + if mb != 2 && (state&C.GDK_BUTTON2_MASK) != 0 { + me.Held = append(me.Held, 2) + } + if mb != 3 && (state&C.GDK_BUTTON3_MASK) != 0 { + me.Held = append(me.Held, 3) + } + // don't check GDK_BUTTON4_MASK or GDK_BUTTON5_MASK because those are for the scrolling buttons mentioned above + // GDK expressly does not support any more buttons in the GdkModifierType; see https://git.gnome.org/browse/gtk+/tree/gdk/x11/gdkdevice-xi2.c#n763 (thanks mclasen in irc.gimp.net/#gtk+) + me.Pos = image.Pt(int(x), int(y)) + C.gtk_widget_get_size_request(widget, &areawidth, &areaheight) + if !me.Pos.In(image.Rect(0, 0, int(areawidth), int(areaheight))) { // outside the actual Area; no event + return + } + // and finally, if the button ID >= 8, continue counting from 4, as above and as in the MouseEvent spec + if me.Down >= 8 { + me.Down -= 4 + } + if me.Up >= 8 { + me.Up -= 4 + } + a.handler.Mouse(me) +} + +// convenience name to make our intent clear +const continueEventChain C.gboolean = C.FALSE +const stopEventChain C.gboolean = C.TRUE + +// checking for a mouse click that makes the program/window active is meaningless on GTK+: it's a property of the window manager/X11, and it's the WM that decides if the program should become active or not +// however, one thing is certain: the click event will ALWAYS be sent (to the window that the X11 decides to send it to) +// I assume the same is true for Wayland +// thanks Chipzz in irc.gimp.net/#gtk+ + +//export our_area_button_press_event_callback +func our_area_button_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + // clicking doesn't automatically transfer keyboard focus; we must do so manually (thanks tristan in irc.gimp.net/#gtk+) + C.gtk_widget_grab_focus(widget) + e := (*C.GdkEventButton)(unsafe.Pointer(event)) + me := MouseEvent{ + // GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent() + Down: uint(e.button), + } + + var maxTime C.gint + var maxDistance C.gint + + if e._type != C.GDK_BUTTON_PRESS { + // ignore GDK's generated double-clicks and beyond; we handled those ourselves below + return continueEventChain + } + a := (*area)(unsafe.Pointer(data)) + // e.time is unsigned and in milliseconds + // maxTime is also milliseconds; despite being gint, it is only allowed to be positive + // maxDistance is also only allowed to be positive + settings := C.gtk_widget_get_settings(widget) + C.gtkGetDoubleClickSettings(settings, &maxTime, &maxDistance) + me.Count = a.clickCounter.click(me.Down, int(e.x), int(e.y), + uintptr(e.time), uintptr(maxTime), + int(maxDistance), int(maxDistance)) + + finishMouseEvent(widget, data, me, me.Down, e.x, e.y, e.state, e.window) + return continueEventChain +} + +var area_button_press_event_callback = C.GCallback(C.our_area_button_press_event_callback) + +//export our_area_button_release_event_callback +func our_area_button_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + e := (*C.GdkEventButton)(unsafe.Pointer(event)) + me := MouseEvent{ + // GDK button ID == our button ID with some exceptions taken care of by finishMouseEvent() + Up: uint(e.button), + } + finishMouseEvent(widget, data, me, me.Up, e.x, e.y, e.state, e.window) + return continueEventChain +} + +var area_button_release_event_callback = C.GCallback(C.our_area_button_release_event_callback) + +//export our_area_motion_notify_event_callback +func our_area_motion_notify_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + e := (*C.GdkEventMotion)(unsafe.Pointer(event)) + me := MouseEvent{} + finishMouseEvent(widget, data, me, 0, e.x, e.y, e.state, e.window) + return continueEventChain +} + +var area_motion_notify_event_callback = C.GCallback(C.our_area_motion_notify_event_callback) + +// we want switching away from the control to reset the double-click counter, like with WM_ACTIVATE on Windows +// according to tristan in irc.gimp.net/#gtk+, doing this on enter-notify-event and leave-notify-event is correct (and it seems to be true in my own tests; plus the events DO get sent when switching programs with the keyboard (just pointing that out)) +// differentiating between enter-notify-event and leave-notify-event is unimportant + +//export our_area_enterleave_notify_event_callback +func our_area_enterleave_notify_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + a := (*area)(unsafe.Pointer(data)) + a.clickCounter.reset() + return continueEventChain +} + +var area_enterleave_notify_event_callback = C.GCallback(C.our_area_enterleave_notify_event_callback) + +// shared code for doing a key event +func doKeyEvent(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer, up bool) bool { + var ke KeyEvent + + e := (*C.GdkEventKey)(unsafe.Pointer(event)) + a := (*area)(unsafe.Pointer(data)) + keyval := e.keyval + // get modifiers now in case a modifier was pressed + state := translateModifiers(e.state, e.window) + ke.Modifiers = makeModifiers(state) + if extkey, ok := extkeys[keyval]; ok { + ke.ExtKey = extkey + } else if mod, ok := modonlykeys[keyval]; ok { + ke.Modifier = mod + // don't include the modifier in ke.Modifiers + ke.Modifiers &^= mod + } else if xke, ok := fromScancode(uintptr(e.hardware_keycode) - 8); ok { + // see events_notdarwin.go for details of the above map lookup + // one of these will be nonzero + ke.Key = xke.Key + ke.ExtKey = xke.ExtKey + } else { // no match + return false + } + ke.Up = up + return a.handler.Key(ke) +} + +//export our_area_key_press_event_callback +func our_area_key_press_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + if doKeyEvent(widget, event, data, false) == true { + return stopEventChain + } + return continueEventChain +} + +var area_key_press_event_callback = C.GCallback(C.our_area_key_press_event_callback) + +//export our_area_key_release_event_callback +func our_area_key_release_event_callback(widget *C.GtkWidget, event *C.GdkEvent, data C.gpointer) C.gboolean { + if doKeyEvent(widget, event, data, true) == true { + return stopEventChain + } + return continueEventChain +} + +var area_key_release_event_callback = C.GCallback(C.our_area_key_release_event_callback) + +var extkeys = map[C.guint]ExtKey{ + C.GDK_KEY_Escape: Escape, + C.GDK_KEY_Insert: Insert, + C.GDK_KEY_Delete: Delete, + C.GDK_KEY_Home: Home, + C.GDK_KEY_End: End, + C.GDK_KEY_Page_Up: PageUp, + C.GDK_KEY_Page_Down: PageDown, + C.GDK_KEY_Up: Up, + C.GDK_KEY_Down: Down, + C.GDK_KEY_Left: Left, + C.GDK_KEY_Right: Right, + C.GDK_KEY_F1: F1, + C.GDK_KEY_F2: F2, + C.GDK_KEY_F3: F3, + C.GDK_KEY_F4: F4, + C.GDK_KEY_F5: F5, + C.GDK_KEY_F6: F6, + C.GDK_KEY_F7: F7, + C.GDK_KEY_F8: F8, + C.GDK_KEY_F9: F9, + C.GDK_KEY_F10: F10, + C.GDK_KEY_F11: F11, + C.GDK_KEY_F12: F12, + // numpad numeric keys and . are handled in events_notdarwin.go + C.GDK_KEY_KP_Enter: NEnter, + C.GDK_KEY_KP_Add: NAdd, + C.GDK_KEY_KP_Subtract: NSubtract, + C.GDK_KEY_KP_Multiply: NMultiply, + C.GDK_KEY_KP_Divide: NDivide, +} + +// sanity check +func init() { + included := make([]bool, _nextkeys) + for _, v := range extkeys { + included[v] = true + } + for i := 1; i < int(_nextkeys); i++ { + if i >= int(N0) && i <= int(N9) { // skip numpad numbers and . + continue + } + if i == int(NDot) { + continue + } + if !included[i] { + panic(fmt.Errorf("error: not all ExtKeys defined on Unix (missing %d)", i)) + } + } +} + +var modonlykeys = map[C.guint]Modifiers{ + C.GDK_KEY_Control_L: Ctrl, + C.GDK_KEY_Control_R: Ctrl, + C.GDK_KEY_Alt_L: Alt, + C.GDK_KEY_Alt_R: Alt, + C.GDK_KEY_Meta_L: Alt, + C.GDK_KEY_Meta_R: Alt, + C.GDK_KEY_Shift_L: Shift, + C.GDK_KEY_Shift_R: Shift, + C.GDK_KEY_Super_L: Super, + C.GDK_KEY_Super_R: Super, +} + +func (a *area) widget() *C.GtkWidget { + return a._widget +} + +func (a *area) setParent(p *controlParent) { + a.scroller.setParent(p) +} + +func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation { + return baseallocate(a, x, y, width, height, d) +} + +func (a *area) preferredSize(d *sizing) (width, height int) { + // the preferred size of an Area is its size + return a.width, a.height +} + +func (a *area) commitResize(c *allocation, d *sizing) { + a.scroller.commitResize(c, d) +} + +func (a *area) getAuxResizeInfo(d *sizing) { + // a Label to the left of an Area should be vertically aligned to the top + d.shouldVAlignTop = true +} diff --git a/newctrl/control_unix.go b/newctrl/control_unix.go new file mode 100644 index 0000000..cec2061 --- /dev/null +++ b/newctrl/control_unix.go @@ -0,0 +1,121 @@ +// +build !windows,!darwin + +// 30 july 2014 + +package ui + +import ( + "unsafe" +) + +// #include "gtk_unix.h" +import "C" + +type controlParent struct { + c *C.GtkContainer +} + +type controlSingleWidget struct { + *controlbase + widget *C.GtkWidget +} + +func newControlSingleWidget(widget *C.GtkWidget) *controlSingleWidget { + c := new(controlSingleWidget) + c.controlbase = &controlbase{ + fsetParent: c.setParent, + fpreferredSize: c.preferredSize, + fresize: c.resize, + } + c.widget = widget + return c +} + +func (c *controlSingleWidget) setParent(p *controlParent) { + C.gtk_container_add(p.c, c.widget) + // make sure the new widget is shown if not explicitly hidden + // TODO why did I have this again? + C.gtk_widget_show_all(c.widget) +} + +func (c *controlSingleWidget) preferredSize(d *sizing) (int, int) { + // GTK+ 3 makes this easy: controls can tell us what their preferred size is! + // ...actually, it tells us two things: the "minimum size" and the "natural size". + // The "minimum size" is the smallest size we /can/ display /anything/. The "natural size" is the smallest size we would /prefer/ to display. + // The difference? Minimum size takes into account things like truncation with ellipses: the minimum size of a label can allot just the ellipses! + // So we use the natural size instead. + // There is a warning about height-for-width controls, but in my tests this isn't an issue. + var r C.GtkRequisition + + C.gtk_widget_get_preferred_size(c.widget, nil, &r) + return int(r.width), int(r.height) +} + +func (c *controlSingleWidget) resize(x int, y int, width int, height int, d *sizing) { + // as we resize on size-allocate, we have to also use size-allocate on our children + // this is fine anyway; in fact, this allows us to move without knowing what the container is! + // this is what GtkBox does anyway + // thanks to tristan in irc.gimp.net/#gtk+ + + var r C.GtkAllocation + + r.x = C.int(x) + r.y = C.int(y) + r.width = C.int(width) + r.height = C.int(height) + C.gtk_widget_size_allocate(c.widget, &r) +} + +type scroller struct { + *controlSingleWidget + + scroller *controlSingleWidget + scrollwidget *C.GtkWidget + scrollcontainer *C.GtkContainer + scrollwindow *C.GtkScrolledWindow + + overlay *controlSingleWidget + overlaywidget *C.GtkWidget + overlaycontainer *C.GtkContainer + overlayoverlay *C.GtkOverlay +} + +func newScroller(widget *C.GtkWidget, native bool, bordered bool, overlay bool) *scroller { + s := new(scroller) + s.controlSingleWidget = newControlSingleWidget(widget) + s.scrollwidget = C.gtk_scrolled_window_new(nil, nil) + s.scrollcontainer = (*C.GtkContainer)(unsafe.Pointer(s.scrollwidget)) + s.scrollwindow = (*C.GtkScrolledWindow)(unsafe.Pointer(s.scrollwidget)) + + // any actual changing operations need to be done to the GtkScrolledWindow + // that is, everything /except/ preferredSize() are done to the GtkScrolledWindow + s.scroller = newControlSingleWidget(s.scrollwidget) + s.fsetParent = s.scroller.fsetParent + s.fresize = s.scroller.fresize + + // in GTK+ 3.4 we still technically need to use the separate gtk_scrolled_window_add_with_viewpoint()/gtk_container_add() spiel for adding the widget to the scrolled window + if native { + C.gtk_container_add(s.scrollcontainer, s.widget) + } else { + C.gtk_scrolled_window_add_with_viewport(s.scrollwindow, s.widget) + } + + // give the scrolled window a border (thanks to jlindgren in irc.gimp.net/#gtk+) + if bordered { + C.gtk_scrolled_window_set_shadow_type(s.scrollwindow, C.GTK_SHADOW_IN) + } + + if overlay { + // ok things get REALLY fun now + // we now have to do all of the above again + s.overlaywidget = C.gtk_overlay_new() + s.overlaycontainer = (*C.GtkContainer)(unsafe.Pointer(s.overlaywidget)) + s.overlayoverlay = (*C.GtkOverlay)(unsafe.Pointer(s.overlayoverlay)) + s.overlay = newControlSingleWidget(s.overlaywidget) + s.fsetParent = s.overlay.fsetParent + s.fresize = s.overlay.fresize + C.gtk_container_add(s.overlaycontainer, s.scrollcontainer) + } + + return s +}