I can not remember many examples of extended objects for Nim. For OO languages like Ruby we can extend everything with arbitrary custom fields. For Nim standard types I have never seen something like
type
MyStr = ref object of string
age: int
var m: MyStr = newString(); m.age = 7
Well, composition over inheritance?
But for the high level GTK proxy objects I need the ability to extend objects of course, otherwise the high level wrapper makes not much sense at all. Currently I have all the GUI objects working fine, with procs like newButton() the Nim button proxy object linked with the real GUI element is created. But now users may want to add new fields to the elements, so I need a proc like initButton()? I know we have some init procs in standard lib like initTable(), but I think that name is more about value and reference objects.
I just tried code like this:
type
O = ref object of RootObj
i: int
O1 = ref object of O
j: int
proc newO(): O =
new(result)
result.i = 7
proc initO[T](o: var T) =
assert(o is O)
new(o)
o.i = 7
var o = newO()
echo o.i
var o1: O1
initO(o1)
o1.j = 9
echo o1.i
echo o1.j
#var o1x = initO[O1] # does not work
Well, I think that is what may be desired, so for each GUI widget XXX I can provide beside a newXXX() proc a initXXX() proc which accepts extended objects. Is there a way to reduce
var o1: O1
initO(o1)
into one single statement? The commented out from above (#var o1x = initO[O1]) does not work of course.
Would something like the following work?
# Free Public License 1.0.0
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
import macros
macro make*(e: untyped): auto =
var tp, call: NimNode
let sym = genSym(nskVar)
case e.kind
of nnkCall:
case e[0].kind
of nnkDotExpr:
tp = e[0][0]
call = newCall(e[0][1])
add(call, sym)
for i in 1..len(e)-1:
add(call, e[i])
of nnkIdent:
tp = e[0]
call = newCall(!"init", sym)
else:
error("not a constructor call")
of nnkDotExpr:
tp = e[0]
call = newCall(e[1], sym)
else:
error("not a constructor call")
expectKind(tp, nnkIdent)
result = quote do:
var `sym` = `tp`()
`call`
`sym`
when isMainModule:
import strutils
type Obj = ref object {.inheritable.}
x: int
type Obj2 = ref object of Obj
y: int
proc init(ob: Obj) =
ob.x = 1
proc init(ob: Obj, z: int) =
ob.x = z
proc fromString(ob: Obj, s: string) =
ob.x = s.parseInt
proc fromMin(ob: Obj, a, b: int) =
ob.x = min(a, b)
proc fromPair(ob: Obj2, pair: (int, int)) =
ob.x = pair[0]
ob.y = pair[1]
proc `$`(ob: Obj): string = "Obj(x: " & $ob.x & ")"
proc `$`(ob: Obj2): string = "Obj2(x: " & $ob.x & ", y: " & $ob.y & ")"
var a1 = make Obj.init
var a2 = make Obj.init(2)
var a3 = make Obj.init()
var a4 = make Obj.fromString("99")
var a5 = make Obj.fromMin(314, 2718)
var a6 = make Obj()
var a7 = make Obj2.init()
var a8 = make Obj2.fromString("314")
var a9 = make Obj2.fromPair((10, 20))
echo a1
echo a2
echo a3
echo a4
echo a5
echo a6
echo a7
echo a8
echo a9
Note that you can use methods in lieu of procs, too, if you want type-specific initialization. The macro simply is turned into a constructor call, followed by a call to the initialization proc/method, followed by the expression returning the created object.
That is very interesting, thanks.
(As you know, I am using your module combinatorics.nim in https://github.com/StefanSalewski/nim-gi2. Would be nice if that module would be put into Nim's standard lib -- it works fine, but I have never tried to understand it yet, I guess it is not trivial...)
nimx solves this problem in a way inspired by Apple obj-c frameworks:
type
View = ref object of RootObj
Button = ref object of View
method init*(v: View, r: Rect) {.base.} =
echo "init view"
proc new[T: View](t: typedesc[T], r: Rect): T =
result.new()
result.init(r)
method init*(b: Button, r: Rect) =
procCall b.View.init(r)
echo "init button"
let myButton = Button.new(makeRect(0, 0, 100, 100))
I just tried my initial idea, and indeed it seems to work not bad.
proc gtk_button_new*(): ptr Button00 {.
importc: "gtk_button_new", libprag.}
proc newButton*(): Button =
new(result, finalizeGObject)
result.impl = gtk_button_new()
GC_ref(result)
g_object_add_toggle_ref(result.impl, toggleNotify, addr(result[]))
assert(g_object_get_qdata(result.impl, Quark) == nil)
g_object_set_qdata(result.impl, Quark, addr(result[]))
proc initButton*[T](result: var T) =
assert(result is Button)
new(result, finalizeGObject)
result.impl = gtk_button_new()
GC_ref(result)
g_object_add_toggle_ref(result.impl, toggleNotify, addr(result[]))
assert(g_object_get_qdata(result.impl, Quark) == nil)
g_object_set_qdata(result.impl, Quark, addr(result[]))
I have supported the automatically generated proc newButton() with a manually created initButton(), and indeed the test program below works.
# plain test for high level gi based GTK3 Nim wrapper
# https://github.com/StefanSalewski/nim-gi2
import gtk, glib, gobject
import macros
import strutils
type
XButton = ref object of Button
x: int
# TODO: will be moved to library module!
let Quark = g_quark_from_static_string("NimGIQuark")
# TODO: will be moved to library module!
proc initWithArgv*() =
var
cmdLine{.importc.}: cstringArray
cmdCount{.importc.}: cint
gtk.gtk_init(cmdCount, cmdLine)
proc clickd(button: XButton; arg: string) =
echo arg
echo button.x
proc bye(w: Window; arg: string) =
mainQuit()
echo arg
var ProcID: int
# TODO: this macro will be moved to library module!
macro connect(widget: Widget; signal: string; p: typed; arg: typed): typed =
inc(ProcID)
let wt = getType(widget) # widget type
let at = getType(arg) # argument type
let signalName = ($signal).replace("-", "_") # maybe we should just use plain proc names
let procNameCdecl = newIdentNode("connect_for_signal_cdecl_" & signalName & $ProcID)
let procName = newIdentNode("connect_for_signal_" & signalName & $ProcID)
let scName = newIdentNode("sc" & signalName)
result = quote do:
proc `procNameCdecl`(button: ptr Object00 , data: pointer) {.cdecl.} =
var h: pointer = g_object_get_qdata(button, Quark)
`p`(cast[`wt`](h), cast[`at`](data))
proc `procName`(self: `wt`; p: proc (self: `wt`, arg: `at`); a: `at`) =
`scName`(self, `procNameCdecl`, cast[pointer](a))
`procName`(`widget`, `p`, `arg`)
proc main() =
initWithArgv()
var window: Window = newWindow(WindowType.topLevel)
window.setTitle("First Test")
window.setBorderWidth(10)
var
box = newBox(Orientation.vertical, 0)
button1: XButton
button2 = newButtonWithLabel("Wrapper")
initButton(button1)
button1.setLabel("Nim GI")
button1.x = 99
connect(button1, "clicked", clickd, "Hello")
connect(window, "destroy", bye, "Bye")
#button2.newconnect("clicked", (proc(button: Button; arg: string) = echo arg), "Bye")
box.add(button1)
box.add(button2)
window.add(box)
window.showAll
let p = button1.getParent
assert(p == box)
gtk.gtkMain()
main()
The connect makro is type safe, allows to pass arbitrary arguments and works with extended objects like XButton from above.