diff --git a/docs-src/subclassing.md b/docs-src/subclassing.md new file mode 100644 index 00000000..6b64e4e0 --- /dev/null +++ b/docs-src/subclassing.md @@ -0,0 +1,44 @@ +There is no direct support in gobbi for subclassing +GObject derived classes +or for implementing interfaces. +Following exploration and the production of a +proof of concept for class derivation, +it became apparent that an awful lots of work +would have to be put it for a fairly small reward. +Lots more generation code would have to be written, +and many tens of thousands of new lines of code +would be generated. +And even then there would likely be many +cases not covered. + +Deriving classes, implementing interfaces, +and implementing virtual functions are +unlikely to be particularly common activities +in gobbi based applications. +So for now at least adding support to make this +easy has been put to one side. + +## example +Instead of providing direct support in gobbi, a +[subclassing](https://github.com/pekim/gobbi/blob/master/example/subclass-drawingarea) +example is provided. +This illustrates how the DrawingAreas widget +can be subclassed, some virtual functions implemented. + +## pre-requisites +For the most part using gobbi does not require +a detailed knowledge C or gobject. +With some familiarity with Go, Gtk and perhaps +a passing knowledge of cgo, +it should be possible to write an application +with gobbi. + +However subclassing and the implementation of +virtual functions will require a bit +more knowledge. + +- comfort with Go +- familiarity with C +- familiarity with cgo +- an understanding of the [gobject base class](https://developer.gnome.org/gobject/stable/chapter-gobject.html) + diff --git a/docs/api.html b/docs/api.html index 784c7bd6..6df61996 100644 --- a/docs/api.html +++ b/docs/api.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/application-lifecycle.html b/docs/application-lifecycle.html index 4ebf23ca..e8042f94 100644 --- a/docs/application-lifecycle.html +++ b/docs/application-lifecycle.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/build-tags.html b/docs/build-tags.html index 269546ec..0b69d71b 100644 --- a/docs/build-tags.html +++ b/docs/build-tags.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/casting.html b/docs/casting.html index f24a59f2..1a2f4073 100644 --- a/docs/casting.html +++ b/docs/casting.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/getting-started.html b/docs/getting-started.html index f63859c7..495583b3 100644 --- a/docs/getting-started.html +++ b/docs/getting-started.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/goroutines.html b/docs/goroutines.html index 2369f310..53033e4f 100644 --- a/docs/goroutines.html +++ b/docs/goroutines.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/gvalue.html b/docs/gvalue.html index 25f6831e..2af0f04d 100644 --- a/docs/gvalue.html +++ b/docs/gvalue.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/index.html b/docs/index.html index ec1feb81..f19e3f63 100644 --- a/docs/index.html +++ b/docs/index.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/reference-counting.html b/docs/reference-counting.html index 6b85939b..3279262a 100644 --- a/docs/reference-counting.html +++ b/docs/reference-counting.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/signal-handling.html b/docs/signal-handling.html index 14769ca1..71d14fa4 100644 --- a/docs/signal-handling.html +++ b/docs/signal-handling.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/docs/subclassing.html b/docs/subclassing.html new file mode 100644 index 00000000..64038e61 --- /dev/null +++ b/docs/subclassing.html @@ -0,0 +1,123 @@ + + + + + + + Subclassing - gobbi + + + + + + + + + +
    + gobbi + + + + github repo + +
    + + +
    + + + +
    +

    Subclassing

    +

    There is no direct support in gobbi for subclassing +GObject derived classes +or for implementing interfaces. +Following exploration and the production of a +proof of concept for class derivation, +it became apparent that an awful lots of work +would have to be put it for a fairly small reward. +Lots more generation code would have to be written, +and many tens of thousands of new lines of code +would be generated. +And even then there would likely be many +cases not covered.

    + +

    Deriving classes, implementing interfaces, +and implementing virtual functions are +unlikely to be particularly common activities +in gobbi based applications. +So for now at least adding support to make this +easy has been put to one side.

    + +

    example

    + +

    Instead of providing direct support in gobbi, a +subclassing +example is provided. +This illustrates how the DrawingAreas widget +can be subclassed, some virtual functions implemented.

    + +

    pre-requisites

    + +

    For the most part using gobbi does not require +a detailed knowledge C or gobject. +With some familiarity with Go, Gtk and perhaps +a passing knowledge of cgo, +it should be possible to write an application +with gobbi.

    + +

    However subclassing and the implementation of +virtual functions will require a bit +more knowledge.

    + + + +
    +
    + + diff --git a/docs/variadic-functions.html b/docs/variadic-functions.html index 5edd8025..9ba49d9e 100644 --- a/docs/variadic-functions.html +++ b/docs/variadic-functions.html @@ -57,6 +57,9 @@
  • Reference counting
  • +
  • + Subclassing +
  • API docs
  • diff --git a/example/subclass-drawingarea/README.md b/example/subclass-drawingarea/README.md new file mode 100644 index 00000000..290ea7fd --- /dev/null +++ b/example/subclass-drawingarea/README.md @@ -0,0 +1,25 @@ +# subclassing example + +This example demonstrates an approach for +subclassing a gtk widget. +A subclass of GtkDrawingArea is registered, +and two virtual functions are implemented. + +The `draw` virtual function calls a Go function +that marshals the arguments in to Go objects, +and calls another function to draw in a cairo context. +The same result could have been achieved by +connecting to the `draw` signal instead, +(The [custom-drawing](https://github.com/pekim/gobbi/blob/master/example/custom-drawing/main.go) +example uses that approach.) +however the point of this example is to +illustrate class derivation and +the overriding of virtual functions. + +The `adjust_size_request` virtual function's +implementation is trivial, +and is implemented entirely in C. + +For more background about subclassing with gobbi see +[subclassing](https://pekim.github.io/gobbi/subclassing) +in the documentation. diff --git a/example/subclass-drawingarea/da/drawingarea-derived-class.go b/example/subclass-drawingarea/da/drawingarea-derived-class.go new file mode 100644 index 00000000..e7da0660 --- /dev/null +++ b/example/subclass-drawingarea/da/drawingarea-derived-class.go @@ -0,0 +1,102 @@ +package da + +/* +#cgo pkg-config: gtk+-3.0 + +#include +#include +#include "drawingarea-derived.h" +*/ +import "C" + +import ( + "github.com/pekim/gobbi/lib/cairo" + "sync" + "unsafe" +) + +type DrawingAreaDerivedNative *C.GtkDrawingArea + +/* + A map of ids to Go objects representing the derived widget instance. + The ids are allocated by incrementing an int for each new instance. + + An id is stored in a C class instance struct when the instance is created. + It is later retrieved in the Go virtual function Draw, to lookup the + correspond Go object in the map. +*/ +var ( + daIntancesLock sync.Mutex + daInstanceId = 0 + daInstances = make(map[int]*DrawingAreaDerived) +) + +type DrawingAreaDerivedClass struct { + gtype C.GType +} + +/* + Register a new class derived from GtkDrawingArea. +*/ +func DrawingAreaDerive() *DrawingAreaDerivedClass { + var typeInfo C.GTypeInfo + typeInfo.class_size = C.sizeof_GtkDrawingAreaClass + typeInfo.instance_size = C.sizeof_da_d_instance + typeInfo.class_init = C.GClassInitFunc(C.drawing_area_class_init) + + cTypeName := C.CString("drawing_area_derived") + defer C.free(unsafe.Pointer(cTypeName)) + + gtype := C.g_type_register_static(C.GTK_TYPE_DRAWING_AREA, cTypeName, &typeInfo, 0) + + class := &DrawingAreaDerivedClass{ + gtype: gtype, + } + + return class +} + +/* + Create a new instance of the derived class, and use a + Go object (DrawingAreadDerived) to represent it. +*/ +func (c *DrawingAreaDerivedClass) New() *DrawingAreaDerived { + native := (DrawingAreaDerivedNative)(C.g_object_newv(c.gtype, 0, nil)) + + instance := &DrawingAreaDerived{native: native} + instance.init() + + daIntancesLock.Lock() + defer daIntancesLock.Unlock() + daInstanceId++ + // map the id to the Go object + daInstances[daInstanceId] = instance + + // note the id in the class's instance data struct + daC := (*C.da_d_instance)(unsafe.Pointer(native)) + daC.instanceId = C.int(daInstanceId) + + return instance +} + +/* + drawingAreaDerivedFromCWidget uses the stored id to lookup a + DrawingAreaDerived. +*/ +func drawingAreaDerivedFromCWidget(widgetC unsafe.Pointer) *DrawingAreaDerived { + daC := (*C.da_d_instance)(widgetC) + instanceId := int(daC.instanceId) + return daInstances[instanceId] +} + +// DrawingAreaDraw is called from the C virtual function. +// +//export DrawingAreaDraw +func DrawingAreaDraw(widgetC *C.GtkWidget, contextC *C.cairo_t) C.gboolean { + cr := cairo.ContextNewFromC(unsafe.Pointer(contextC)) + + da := drawingAreaDerivedFromCWidget(unsafe.Pointer(widgetC)) + da.Draw(cr) + + return C.FALSE +} diff --git a/example/subclass-drawingarea/da/drawingarea-derived.c b/example/subclass-drawingarea/da/drawingarea-derived.c new file mode 100644 index 00000000..3db26d16 --- /dev/null +++ b/example/subclass-drawingarea/da/drawingarea-derived.c @@ -0,0 +1,28 @@ +#include +#include "_cgo_export.h" + +// virtual function implementation +gboolean drawing_area_vf_draw(GtkWidget *widget, cairo_t *cr) { + // call Go function + return DrawingAreaDraw(widget, cr); +} + +// virtual function implementation +void drawing_area_vf_adjust_size_request( + GtkWidget *widget, + GtkOrientation orientation, + gint *minimum_size, + gint *natural_size) +{ + *minimum_size = 100; + *natural_size = 100; +} + +void drawing_area_class_init(GtkDrawingAreaClass *g_class, gpointer class_data) { + GtkWidgetClass *widget_class; + widget_class = (GtkWidgetClass*) g_class; + + // override virtual functions + widget_class->draw = drawing_area_vf_draw; + widget_class->adjust_size_request = drawing_area_vf_adjust_size_request; +} diff --git a/example/subclass-drawingarea/da/drawingarea-derived.go b/example/subclass-drawingarea/da/drawingarea-derived.go new file mode 100644 index 00000000..d66a6bbf --- /dev/null +++ b/example/subclass-drawingarea/da/drawingarea-derived.go @@ -0,0 +1,56 @@ +package da + +import ( + "github.com/pekim/gobbi/lib/cairo" + "github.com/pekim/gobbi/lib/gtk" + "math" + "unsafe" +) + +// DrawingAreaDerived represent an instance of the subclassed widget. +type DrawingAreaDerived struct { + native DrawingAreaDerivedNative + + red float64 + green float64 + blue float64 +} + +func (d *DrawingAreaDerived) init() { + // default to a mid grey + d.SetColour(0.5, 0.5, 0.5) +} + +func (d *DrawingAreaDerived) SetColour(r, g, b float64) { + d.red = r + d.green = g + d.blue = b +} + +func (d *DrawingAreaDerived) Draw(cr *cairo.Context) { + widget := d.DrawingArea().Widget() + + // find the dimensions that the widget's been allocated, and + // therefore the size of the area to draw in + alloc := widget.GetAllocation() + height := float64(alloc.Height) + width := float64(alloc.Width) + + // render background first + gtk.RenderBackground(widget.GetStyleContext(), cr, + 0, 0, width, height) + + // an arc that describes a circle to the path + cr.Arc(width/2.0, height/2.0, math.Min(width, height)/2.0, 0, 2*math.Pi) + + // the circle's fill colour + cr.SetSourceRGB(d.red, d.green, d.blue) + + // fill the path (that describes a circle) + cr.Fill() +} + +// DrawingArea upcasts to *DrawingArea +func (recv *DrawingAreaDerived) DrawingArea() *gtk.DrawingArea { + return gtk.DrawingAreaNewFromC(unsafe.Pointer(recv.native)) +} diff --git a/example/subclass-drawingarea/da/drawingarea-derived.h b/example/subclass-drawingarea/da/drawingarea-derived.h new file mode 100644 index 00000000..b44345e5 --- /dev/null +++ b/example/subclass-drawingarea/da/drawingarea-derived.h @@ -0,0 +1,13 @@ +#include + +// The instance struct for the class that's derived from GtkDrawingArea. +typedef struct da_d_instance { + GtkDrawingArea base; + + // somewhere to keep an id that can be used to later lookup + // a instance of the Go DrawingAreaDerived type. + int instanceId; +} da_d_instance; + + +extern void drawing_area_class_init(GtkDrawingAreaClass *g_class, gpointer class_data); diff --git a/example/subclass-drawingarea/main.go b/example/subclass-drawingarea/main.go new file mode 100644 index 00000000..0e80a8d4 --- /dev/null +++ b/example/subclass-drawingarea/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/pekim/gobbi/example/subclass-drawingarea/da" + "github.com/pekim/gobbi/lib/gtk" + "os" +) + +func main() { + gtk.Init(os.Args) + + // register the subclass + daClass := da.DrawingAreaDerive() + + window := gtk.WindowNew(gtk.GTK_WINDOW_TOPLEVEL) + window.SetTitle("A window title") + window.SetDefaultSize(300, 300) + + hbox := gtk.BoxNew(gtk.GTK_ORIENTATION_HORIZONTAL, 10) + + da1 := daClass.New() + hbox.PackStart(da1.DrawingArea().Widget(), true, true, 0) + + da2 := daClass.New() + da2.SetColour(0.7, 0.2, 0.2) + hbox.PackStart(da2.DrawingArea().Widget(), true, true, 0) + + window.Container().Add(hbox.Widget()) + window.Widget().ConnectDestroy(gtk.MainQuit) + window.Widget().ShowAll() + + gtk.Main() +} diff --git a/internal/cmd/docs/main.go b/internal/cmd/docs/main.go index e63046d3..d3d2931c 100644 --- a/internal/cmd/docs/main.go +++ b/internal/cmd/docs/main.go @@ -28,6 +28,7 @@ var pages = []Page{ Page{File: "variadic-functions", Title: "Variadic functions"}, Page{File: "gvalue", Title: "gobject.Value"}, Page{File: "reference-counting", Title: "Reference counting"}, + Page{File: "subclassing", Title: "Subclassing"}, Page{File: "api", Title: "API docs"}, }