From 575b3419877828a7c28e8faf748b8a26bcdc299b Mon Sep 17 00:00:00 2001 From: Mike D Pilsbury Date: Mon, 3 Jun 2019 19:30:58 +0100 Subject: [PATCH] Add an example of subclassing a gtk widget. Half-baked subclass example. Remove unneeded types from derived class example. Move drawing in to a receiver method. Split derived class and instance across two files. Use a struct instead of dodgy pointer maths. Add another virtual function override to example. Add colour to the example derived widget. Add more comments explaining subclassing example. Add some documentation around subclassing. Reference custom-drawing example from subclassing example. --- docs-src/subclassing.md | 44 +++++++ docs/api.html | 3 + docs/application-lifecycle.html | 3 + docs/build-tags.html | 3 + docs/casting.html | 3 + docs/getting-started.html | 3 + docs/goroutines.html | 3 + docs/gvalue.html | 3 + docs/index.html | 3 + docs/reference-counting.html | 3 + docs/signal-handling.html | 3 + docs/subclassing.html | 123 ++++++++++++++++++ docs/variadic-functions.html | 3 + example/subclass-drawingarea/README.md | 25 ++++ .../da/drawingarea-derived-class.go | 102 +++++++++++++++ .../da/drawingarea-derived.c | 28 ++++ .../da/drawingarea-derived.go | 56 ++++++++ .../da/drawingarea-derived.h | 13 ++ example/subclass-drawingarea/main.go | 33 +++++ internal/cmd/docs/main.go | 1 + 20 files changed, 458 insertions(+) create mode 100644 docs-src/subclassing.md create mode 100644 docs/subclassing.html create mode 100644 example/subclass-drawingarea/README.md create mode 100644 example/subclass-drawingarea/da/drawingarea-derived-class.go create mode 100644 example/subclass-drawingarea/da/drawingarea-derived.c create mode 100644 example/subclass-drawingarea/da/drawingarea-derived.go create mode 100644 example/subclass-drawingarea/da/drawingarea-derived.h create mode 100644 example/subclass-drawingarea/main.go 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.

    + +
      +
    • comfort with Go
    • +
    • familiarity with C
    • +
    • familiarity with cgo
    • +
    • an understanding of the gobject base class
    • +
    + +
    +
    + + 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"}, }