diff --git a/README.adoc b/README.adoc index 9d4a986..dd9fd46 100644 --- a/README.adoc +++ b/README.adoc @@ -1789,6 +1789,100 @@ proc main = main() # 123 lines ---- +== A Listview example using a CellDataFunction + +This example shows how a CellDataFunction can be used to +customize cells of a Tree- or Listview. + +[[celldatafunction.nim]] +[source,nim] +.celldatafunction.nim +---- +# This example shows how to apply a CellDataFunc to a GtkTreeView +# C example code was provided by A.Krause in chapter 8 of his book +import gintro/[gtk, gobject, glib] + +const + Color = 0 + Columns = 1 + clr = ["00", "33", "66", "99", "CC", "FF"] + +proc bye(w: Window) = + mainQuit() + echo "Bye..." + +proc toStringVal(s: string): Value = + let gtype = gStringGetType() # typeFromName("gchararray") + discard init(result, gtype) + setString(result, s) + +proc toBoolVal(b: bool): Value = + let gtype = gBooleanGetType() # typeFromName("gboolean") + discard init(result, gtype) + setBoolean(result, b) + +# our Nim function +proc cellDataFuncN(column: TreeViewColumn; renderer: CellRenderer; + model: TreeModel; iter: TreeIter, data: TreeViewColumn) = + ## Get the color string stored by the column and make it the foreground color. + # for testing that optional args work, we pass a TreeViewColumn and echo its title + echo data.title + var val: Value + model.getValue(iter, Color, val) + let text = val.getString + val.unset # is this necessary? + setProperty(renderer, "foreground", toStringVal("#FFFFFF")) + setProperty(renderer, "foreground-set", toBoolVal(true)) + setProperty(renderer, "background", toStringVal(text)) + setProperty(renderer, "background-set", toBoolVal(true)) + setProperty(renderer, "text", toStringVal(text)) + +## Add three columns to the GtkTreeView. All three of the columns will be +## displayed as text, although one is a gboolean value and another is +## an integer. +proc setupTreeView(treeview: TreeView) = + let renderer = gtk.newCellRendererText() + let column = newTreeViewColumn() + column.title = "Standard Colors" + column.packStart(renderer, expand = true) + column.addAttribute(renderer, "text", Color) + discard treeview.appendColumn(column) + column.setCellDataFunc(renderer, cellDataFuncN, column) + column.setCellDataFunc(renderer) # test unsetting! + column.setCellDataFunc(renderer, nil) + column.unsetCellDataFunc(renderer) + column.setCellDataFunc(renderer, cellDataFuncN, column) + +proc main = + var iter: TreeIter + gtk.init() + let window = newWindow() + window.setTitle("Color List") + window.setBorderWidth(10) + window.setSizeRequest(250, 175) + window.connect("destroy", bye) + let treeview = newTreeView() + setupTreeView(treeview) + let gtype = typeFromName("gchararray") + let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast + ## Add all of the products to the GtkListStore. + for i in 0 ..< 6: + for j in 0 ..< 6: + for k in 0 ..< 6: + let color: string = "#" & clr[i] & clr[j] & clr[k] + store.append(iter) + store.setValue(iter, Color, toStringVal(color)) + treeView.setModel(store) + let scrolledWin = newScrolledWindow(nil, nil) + scrolledWin.setPolicy(PolicyType.automatic, PolicyType.automatic) + scrolledWin.add(treeview) + window.add(scrolledWin) + window.showAll + gtk.main() + +main() +---- + == A more advanced example for cairo drawing with zooming, panning, scrolling The following code is a plain Nim version of a drawing demo which I wrote some years ago in Ruby (http://ssalewski.de/PetEd-Demo.html.en). diff --git a/examples/gtk3/celldatafunction.nim b/examples/gtk3/celldatafunction.nim new file mode 100644 index 0000000..e9c7fb6 --- /dev/null +++ b/examples/gtk3/celldatafunction.nim @@ -0,0 +1,83 @@ +# This example shows how to apply a CellDataFunc to a GtkTreeView +# C example code was provided by A.Krause in chapter 8 of his book +import gintro/[gtk, gobject, glib] + +const + Color = 0 + Columns = 1 + clr = ["00", "33", "66", "99", "CC", "FF"] + +proc bye(w: Window) = + mainQuit() + echo "Bye..." + +proc toStringVal(s: string): Value = + let gtype = gStringGetType() # typeFromName("gchararray") + discard init(result, gtype) + setString(result, s) + +proc toBoolVal(b: bool): Value = + let gtype = gBooleanGetType() # typeFromName("gboolean") + discard init(result, gtype) + setBoolean(result, b) + +# our Nim function +proc cellDataFuncN(column: TreeViewColumn; renderer: CellRenderer; + model: TreeModel; iter: TreeIter, data: TreeViewColumn) = + ## Get the color string stored by the column and make it the foreground color. + # for testing that optional args work, we pass a TreeViewColumn and echo its title + echo data.title + var val: Value + model.getValue(iter, Color, val) + let text = val.getString + val.unset # is this necessary? + setProperty(renderer, "foreground", toStringVal("#FFFFFF")) + setProperty(renderer, "foreground-set", toBoolVal(true)) + setProperty(renderer, "background", toStringVal(text)) + setProperty(renderer, "background-set", toBoolVal(true)) + setProperty(renderer, "text", toStringVal(text)) + +## Add three columns to the GtkTreeView. All three of the columns will be +## displayed as text, although one is a gboolean value and another is +## an integer. +proc setupTreeView(treeview: TreeView) = + let renderer = gtk.newCellRendererText() + let column = newTreeViewColumn() + column.title = "Standard Colors" + column.packStart(renderer, expand = true) + column.addAttribute(renderer, "text", Color) + discard treeview.appendColumn(column) + column.setCellDataFunc(renderer, cellDataFuncN, column) + column.setCellDataFunc(renderer) # test unsetting! + column.setCellDataFunc(renderer, nil) + column.unsetCellDataFunc(renderer) + column.setCellDataFunc(renderer, cellDataFuncN, column) + +proc main = + var iter: TreeIter + gtk.init() + let window = newWindow() + window.setTitle("Color List") + window.setBorderWidth(10) + window.setSizeRequest(250, 175) + window.connect("destroy", bye) + let treeview = newTreeView() + setupTreeView(treeview) + let gtype = typeFromName("gchararray") + let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast + ## Add all of the products to the GtkListStore. + for i in 0 ..< 6: + for j in 0 ..< 6: + for k in 0 ..< 6: + let color: string = "#" & clr[i] & clr[j] & clr[k] + store.append(iter) + store.setValue(iter, Color, toStringVal(color)) + treeView.setModel(store) + let scrolledWin = newScrolledWindow(nil, nil) + scrolledWin.setPolicy(PolicyType.automatic, PolicyType.automatic) + scrolledWin.add(treeview) + window.add(scrolledWin) + window.showAll + gtk.main() + +main() diff --git a/gintro/gimpl.nim b/gintro/gimpl.nim index 0fe98b1..a593e73 100644 --- a/gintro/gimpl.nim +++ b/gintro/gimpl.nim @@ -302,3 +302,122 @@ proc $1(a: $2): culong {.discardable.} = $1($4) """ % [$procName, ats, $procNameCdecl, $arg] result = parseStmt(r1s & r2s) + +################################################### + +### CellDataFunc, +# see https://developer.gnome.org/gtk3/stable/GtkTreeViewColumn.html#gtk-tree-view-column-set-cell-data-func + +# the low level GTK interface: +# proc cellDataFunc(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; +# model00: ptr TreeModel00; iter: TreeIter; data: pointer) {.cdecl.} + +# unset -- the macro can do this already +proc unsetCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer) = + setCellDataFunc(column, renderer, nil, nil, nil) + +# set a function with this parameter list and one more optional argument +# proc cellDataFunc(column: TreeViewColumn; renderer: CellRenderer; model: TreeModel; iter: TreeIter) +# if there is no optional arg, then we make it a pointer with nil value. +# if arg is a ref, then we apply GC_ref() and pass it +# if arg is a ptr, then we pass it. (but we never free that object) +# if arg is a value type, then we replace it with a copy of a ref type to guaranty lifetime +macro setCellDataFunc*(column: TreeViewColumn; renderer: CellRenderer; fn: untyped = nil; arg: typed = nil): untyped = + + if fn == nil: # unset + result = parseStmt("setCellDataFunc($1, $2, nil, nil, nil)" % [$column, $renderer]) + return + + var CdfID {.compileTime, global.}: int # unique id for our generated cell data functions + inc(CdfID) + var ats: string # the type of optional argument as string (arg type string) + var argStr: string # the value of optional argument as string + var atsfix: string # we have to prefix with "ref " if arg is avalue type + if arg == nil: # no optional argument, so fake a nil pointer + argStr = "nil" + ats = "pointer" + else: + argStr = $(arg.toStrLit) + ats = $getTypeInst(arg).toStrLit + + if getTypeInst(arg).typeKind != ntyRef and getTypeInst(arg).typeKind != ntyPtr: + atsFix = "ref " + + let procName = "celldatafunc_" & $CdfID # our init code + let procNameCdecl = "celldatafunc_cdecl_" & $CdfID # the proc that is called from GTK + var procNameDestroy: string # the proc that GTK calls when we unset the cellDataFunc + # first we generate the destroy proc + var r0s: string + + # GTK should unref the optional argument later, so we generate a destroy proc which GTK can call + if arg != nil and getTypeInst(arg).typeKind != ntyPtr: + procNameDestroy = "celldatafunc_destroy" & $CdfID + r0s =""" +proc $1(p: pointer) {.cdecl.} = + # echo "GC_Unref(arg)" + GC_Unref(cast[$2](p)) +""" % [$procNameDestroy, atsFix & ats] + else: + procNameDestroy = "nil" + r0s = "" + +# now we generate the proc which GTK will call for each cell of the treeview. g_object_get_qdata() is +# the GTK function which retrieves the Nim proxy objects from the plain GTK data objects. + var r1s = """ +proc $1(column00: ptr TreeViewColumn00; renderer00: ptr CellRenderer00; + model00: ptr TreeModel00; iter: TreeIter; pdata: pointer) {.cdecl.} = + let column = cast[TreeViewColumn](g_object_get_qdata(column00, Quark)) + let renderer = cast[CellRenderer](g_object_get_qdata(renderer00, Quark)) + let model = cast[TreeModel](g_object_get_qdata(model00, Quark)) +""" % [$procNameCdecl] + if arg == nil: + r1s.add """ + $1(column, renderer, model, iter) # call the user provided Nim proc +""" % [$fn] + elif getTypeInst(arg).typeKind == ntyRef or getTypeInst(arg).typeKind == ntyPtr: + r1s.add """ + let a = cast[$2](pdata) + $1(column, renderer, model, iter, a) # call the user provided Nim proc +""" % [$fn, ats] + else: # arg was a value type, but we pass a copy of ref type + r1s.add """ + let a = cast[ref $2](pdata)[] + $1(column, renderer, model, iter, a) # call the user provided Nim proc +""" % [$fn, ats] + +# this is basically only a code block which is called for initialization, mostly +# making a deep copy of optional argument when argumant is not a ref + var r2s: string + if arg == nil: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + setCellDataFunc($3, $4, $5, nil, nil) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + elif getTypeInst(arg).typeKind == ntyPtr: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + elif getTypeInst(arg).typeKind == ntyRef: + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + if not pdata.isNil: # or pdata is pointer): + GC_ref(pdata) + setCellDataFunc($3, $4, $5, cast[pointer](pdata), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + else: # arg is value object + r2s =""" +proc $1(column: TreeViewColumn; renderer: CellRenderer; pdata: $2) = + var ar: ref $2 + new(ar) + deepCopy(ar[], pdata) + GC_ref(ar) + setCellDataFunc($3, $4, $5, cast[pointer](ar), $7) +$1(column, renderer, $6) +""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy] + + result = parseStmt(r0s & r1s & r2s) +# 423 lines \ No newline at end of file