diff --git a/README.adoc b/README.adoc index ce864a0..22f10b8 100644 --- a/README.adoc +++ b/README.adoc @@ -1638,6 +1638,144 @@ make editing the program name entries permanent. That proc needs cstring paramet may be surprising, as we generally use Nim strings. Not a big problem, maybe intended, we may have to check the connect() macro in gimpl.nim. +== And one more Listview example -- with custom cairo drawing + +This example is again a Nim version of a C example from C. Eric Cashon +provided at https://discourse.gnome.org/t/gtk-how-to-draw-on-top-of-gtktreeview/1783/2. + +It draws an rectangular frame on a selected listview cell. For that to work +connectAfter() is used to ensure that the custom cairo drawing occurs after +the widget is drawn by GTK. + +[[overlay_tree1.nim]] +[source,nim] +.overlay_tree1.nim +---- +# nim c overlayTree1.nim +import gintro/[gtk, gdk, glib, gobject, cairo] +import strformat +from strutils import parseInt +const + Id = 0 + Program = 1 + Color = 2 + Color2 = 3 + Columns = 4 + +var + rowG = 0 + columnG = 1 + +proc bye(w: gtk.Window) = + mainQuit() + echo "Bye..." + +proc toStringVal(s: string): Value = + let gtype = typeFromName("gchararray") + discard init(result, gtype) + setString(result, s) + +proc toUIntVal(i: int): Value = + let gtype = typeFromName("guint") + discard init(result, gtype) + setUint(result, i) + +proc toBoolVal(b: bool): Value = + let gtype = typeFromName("gboolean") + discard init(result, gtype) + setBoolean(result, b) + +proc selectCell(treeView: TreeView; path: TreePath; column: TreeViewColumn) = + let str = toString(path) + echo fmt"{str} {getTitle(column)}" + rowG = parseInt(str) + queueDraw(treeView) + +proc drawRectangle(overlay: Overlay; cr: cairo.Context; treeView: TreeView): bool = + echo fmt"Draw Rectangle {rowG} {columnG}" + let path = newTreePathFromIndices(@[rowG.int32]) + echo path.toString + let column = getColumn(treeView, columnG) + var rect: gdk.Rectangle + var x, y: int + treeView.convertBinWindowToWidgetCoords(0, 0, x, y) + cr.save + cr.translate(x.float, y.float) + cr.setLineWidth(2) + cr.setSource(0, 0, 0, 1) + treeView.getCellArea(path, column, rect) + cr.rectangle(rect.x.float + 1, rect.y.float + 1, rect.width.float - 1, rect.height.float - 1) + cr.stroke + cr.restore + return EVENT_PROPAGATE # false + +proc main = + gtk.init() + let window = newWindow() + window.setTitle("Overlay Tree") + window.setPosition(WindowPosition.center) + window.setDefaultSize(500, 500) + window.setBorderWidth(20) + window.connect("destroy", bye) + var iter: TreeIter + let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), + typeFromName("gchararray")] + let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings. + let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"] + for i, n in progNames: + store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original + store.setValue(iter, Id, toUIntVal(i)) + store.setValue(iter, Program, toStringVal(n)) + store.setValue(iter, Color, toStringVal("SpringGreen")) + store.setValue(iter, Color2, toStringVal("cyan")) + let tree = newTreeViewWithModel(store) + tree.setHexpand + tree.setVexpand + tree.setProperty("activate-on-single-click", toBoolVal(true)) + let selection = tree.getSelection + selection.setMode(SelectionMode.single) + let renderer1 = newCellRendererText() + renderer1.setProperty("editable", toBoolVal(false)) + let renderer2 = newCellRendererText() + renderer2.setProperty("editable", toBoolVal(true)) + tree.connect("row-activated", selectCell) + ## Bind the COLOR column to the "cell-background" property. + let column1 = newTreeViewColumn() + column1.setTitle("ID") + column1.packStart(renderer1, true) + column1.addAttribute(renderer1, "text", Id) + column1.addAttribute(renderer1, "cell-background", Color) + discard tree.appendColumn(column1) + let column2 = newTreeViewColumn() + column2.setTitle("Program") + column2.packStart(renderer2, true) + column2.addAttribute(renderer2, "text", Program) + column2.addAttribute(renderer2, "cell-background", Color2) + discard tree.appendColumn(column2) + ## For drawing the outline of the cell. + let overlay = newOverlay() + overlay.setHexpand + overlay.setVexpand + overlay.setAppPaintable + overlay.addOverlay(tree) + overlay.setOverlayPassThrough(tree, true) + overlay.connectAfter("draw", drawRectangle, tree) + let grid = newGrid() + grid.attach(overlay, 0, 0, 1, 1) + window.add(grid) + const cssString = + """treeview{background-color: rgba(0,255,255,1.0); + font-size:30pt} treeview:selected{background-color:rgba(0,255,255,1.0); + color: rgba(0,0,255,1.0);}""" + let provider = newCssProvider() + discard provider.loadFromData(cssString) + getDefaultScreen().addProviderForScreen(provider, STYLE_PROVIDER_PRIORITY_APPLICATION) + window.showAll + gtk.main() + +main() # 123 lines +---- + == 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/css_colored_listview.nim b/examples/gtk3/css_colored_listview.nim index 6a73c62..06c36fd 100644 --- a/examples/gtk3/css_colored_listview.nim +++ b/examples/gtk3/css_colored_listview.nim @@ -2,8 +2,7 @@ ## C. Eric Cashon # nim c css_colored_listview.nim -import gintro/[gtk, glib, gobject] -import gintro/gdk except Window # there is a problem with gdk.Window -- we have to investigate! +import gintro/[gtk, gdk, glib, gobject] const # maybe we should use Nim's enum here? Id = 0 Program = 1 @@ -11,7 +10,7 @@ const # maybe we should use Nim's enum here? Color2 = 3 Columns = 4 -proc bye(w: Window) = +proc bye(w: gtk.Window) = mainQuit() echo "Bye..." diff --git a/examples/gtk3/overlay_tree1.nim b/examples/gtk3/overlay_tree1.nim new file mode 100644 index 0000000..f857985 --- /dev/null +++ b/examples/gtk3/overlay_tree1.nim @@ -0,0 +1,124 @@ +# nim c overlayTree1.nim +# https://discourse.gnome.org/t/gtk-how-to-draw-on-top-of-gtktreeview/1783/2 +import gintro/[gtk, gdk, glib, gobject, cairo] +import strformat +from strutils import parseInt +const + Id = 0 + Program = 1 + Color = 2 + Color2 = 3 + Columns = 4 + +var + rowG = 0 + columnG = 1 + +proc bye(w: gtk.Window) = + mainQuit() + echo "Bye..." + +proc toStringVal(s: string): Value = + let gtype = typeFromName("gchararray") + discard init(result, gtype) + setString(result, s) + +proc toUIntVal(i: int): Value = + let gtype = typeFromName("guint") + discard init(result, gtype) + setUint(result, i) + +proc toBoolVal(b: bool): Value = + let gtype = typeFromName("gboolean") + discard init(result, gtype) + setBoolean(result, b) + +proc selectCell(treeView: TreeView; path: TreePath; column: TreeViewColumn) = + let str = toString(path) + echo fmt"{str} {getTitle(column)}" + rowG = parseInt(str) + queueDraw(treeView) + +proc drawRectangle(overlay: Overlay; cr: cairo.Context; treeView: TreeView): bool = + echo fmt"Draw Rectangle {rowG} {columnG}" + let path = newTreePathFromIndices(@[rowG.int32]) + echo path.toString + let column = getColumn(treeView, columnG) + var rect: gdk.Rectangle + var x, y: int + treeView.convertBinWindowToWidgetCoords(0, 0, x, y) + cr.save + cr.translate(x.float, y.float) + cr.setLineWidth(2) + cr.setSource(0, 0, 0, 1) + treeView.getCellArea(path, column, rect) + cr.rectangle(rect.x.float + 1, rect.y.float + 1, rect.width.float - 1, rect.height.float - 1) + cr.stroke + cr.restore + return EVENT_PROPAGATE # false + +proc main = + gtk.init() + let window = newWindow() + window.setTitle("Overlay Tree") + window.setPosition(WindowPosition.center) + window.setDefaultSize(500, 500) + window.setBorderWidth(20) + window.connect("destroy", bye) + var iter: TreeIter + let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"), + typeFromName("gchararray")] + let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings. + let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"] + for i, n in progNames: + store.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original + store.setValue(iter, Id, toUIntVal(i)) + store.setValue(iter, Program, toStringVal(n)) + store.setValue(iter, Color, toStringVal("SpringGreen")) + store.setValue(iter, Color2, toStringVal("cyan")) + let tree = newTreeViewWithModel(store) + tree.setHexpand + tree.setVexpand + tree.setProperty("activate-on-single-click", toBoolVal(true)) + let selection = tree.getSelection + selection.setMode(SelectionMode.single) + let renderer1 = newCellRendererText() + renderer1.setProperty("editable", toBoolVal(false)) + let renderer2 = newCellRendererText() + renderer2.setProperty("editable", toBoolVal(true)) + tree.connect("row-activated", selectCell) + ## Bind the COLOR column to the "cell-background" property. + let column1 = newTreeViewColumn() + column1.setTitle("ID") + column1.packStart(renderer1, true) + column1.addAttribute(renderer1, "text", Id) + column1.addAttribute(renderer1, "cell-background", Color) + discard tree.appendColumn(column1) + let column2 = newTreeViewColumn() + column2.setTitle("Program") + column2.packStart(renderer2, true) + column2.addAttribute(renderer2, "text", Program) + column2.addAttribute(renderer2, "cell-background", Color2) + discard tree.appendColumn(column2) + ## For drawing the outline of the cell. + let overlay = newOverlay() + overlay.setHexpand + overlay.setVexpand + overlay.setAppPaintable + overlay.addOverlay(tree) + overlay.setOverlayPassThrough(tree, true) + overlay.connectAfter("draw", drawRectangle, tree) + let grid = newGrid() + grid.attach(overlay, 0, 0, 1, 1) + window.add(grid) + const cssString = + """treeview{background-color: rgba(0,255,255,1.0); + font-size:30pt} treeview:selected{background-color:rgba(0,255,255,1.0); + color: rgba(0,0,255,1.0);}""" + let provider = newCssProvider() + discard provider.loadFromData(cssString) + getDefaultScreen().addProviderForScreen(provider, STYLE_PROVIDER_PRIORITY_APPLICATION) + window.showAll + gtk.main() + +main() # 123 lines diff --git a/gintro.nimble b/gintro.nimble index b5f8248..5e96e80 100644 --- a/gintro.nimble +++ b/gintro.nimble @@ -1,6 +1,6 @@ # Package -version = "0.5.4" +version = "0.5.5" author = "Stefan Salewski" description = "High level GObject-Introspection based GTK3/GTK4 bindings" license = "MIT" diff --git a/gintro/gimpl.nim b/gintro/gimpl.nim index cff7f42..0fe98b1 100644 --- a/gintro/gimpl.nim +++ b/gintro/gimpl.nim @@ -37,31 +37,45 @@ proc findSignal(name, obj: NimNode): string = n = getType(n)[1] n = getType(n)[1] -#var ProcID {.compileTime, global.}: int # from file gisup.nim we have: # "remove_editable!CellArea!2!(self: CellArea; renderer: CellRenderer; editable: CellEditable | SpinButton | ComboBox | ... # so for signal remove_editable third parameter of handler proc can be CellEditable OR SpinButton OR ComboBox OR ... # This is because SpinButton, ComboBox provides the CellEditable interface! # https://mail.gnome.org/archives/gtk-list/2017-July/msg00018.html # This interface parameter is in most cases the second parameter of handler proc, but can be the third also. -macro mconnect(widget: gobject.Object; signal: string; p: typed; arg: typed; ignoreArg: bool): untyped = +macro mconnect(widget: gobject.Object; signal: string; p: typed; arg: typed; ignoreArg: bool; sf: static[gobject.ConnectFlags]): untyped = #echo p.symbol.getImpl().toStrLit #echo p.symbol.getImpl().params.toStrLit #echo p.symbol.getImpl().params[2][1].toStrLit var ProcID {.compileTime, global.}: int - var # position and actual type of interface provider, if any ipos = -1 ipro: string #= nil + sfstr = $sf + #if sfstr == "{after}": + # sfstr = "{gobject.ConnectFlag.after}" + sfstr = sfstr.replace("after", "gobject.ConnectFlag.after") + sfstr = sfstr.replace("swapped", "gobject.ConnectFlag.swapped") inc(ProcID) let wt = getType(widget) let at = getTypeInst(arg) + + # echo getType(widget)[0].strVal # ref + # echo getType(widget)[1].strVal # Window:ObjectType + # echo getTypeInst(widget).strVal # Window + #echo at.repr #echo at.typeKind == ntyRef - let wts = ($(wt[1].toStrLit)).replace(":ObjectType") - let ats = $at.toStrLit + # let wts = getTypeInst(widget).strVal + let wts = widget.getTypeInst.owner.strVal & '.' & widget.getTypeInst.strVal # new in v0.5.5 + + let ats = at.toStrLit.strVal + + #assert ats == $(at.toStrLit) + #assert ats == at.toStrLit.strVal + let signalName = ($signal).replace("-", "_") # maybe we should just use plain proc names let procNameCdecl = "connect_for_signal_cdecl_" & signalName & $ProcID let procName = "connect_for_signal_" & signalName & $ProcID @@ -142,7 +156,6 @@ proc $1$2 {.cdecl.} = r1s.add(" new " & names[i] & "1" & "\n") r1s.add(" " & names[i] & "1.impl = " & names[i] & "\n") if not ignoreArg.boolVal: - # resu.add(", cast[$5](data)") if getTypeInst(arg).typeKind == ntyRef: resu.add(", cast[$5](data)") else: @@ -190,16 +203,16 @@ $1($7, $8, $9) if ignoreArg.boolVal: """ proc $1(self: $2; p: proc $3): culong {.discardable.} = - sc$4(self, $5, nil) + sc$4(self, $5, nil, $8) $1($6, $7) -""" % [$procName, wts, ahl, signalName, $procNameCdecl, $(widget.toStrLit), $p] +""" % [$procName, wts, ahl, signalName, $procNameCdecl, $(widget.toStrLit), $p, sfstr] elif getTypeInst(arg).typeKind == ntyRef: """ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} = GC_ref(a) - sc$5(self, $6, cast[pointer](a)) + sc$5(self, $6, cast[pointer](a), $10) $1($7, $8, $9) -""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)] +""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit), sfstr] else: """ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} = @@ -208,20 +221,30 @@ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} = deepCopy(ar[], a) GC_ref(ar) # sc$5(self, $6, cast[pointer](ar[])) - sc$5(self, $6, cast[pointer](ar)) + sc$5(self, $6, cast[pointer](ar), $10) $1($7, $8, $9) -""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)] +""" % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit), sfstr] # echo r2s result.add(parseStmt(r2s)) template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped = - mconnect(widget, signal, p, arg, false) + mconnect(widget, signal, p, arg, false, {}) template connect*(widget: gobject.Object; signal: string; p: typed): untyped = - mconnect(widget, signal, p, "", true) + mconnect(widget, signal, p, "", true, {}) + +template connectAfter*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped = + mconnect(widget, signal, p, arg, false, {gobject.ConnectFlag.after}) + +template connectAfter*(widget: gobject.Object; signal: string; p: typed): untyped = + mconnect(widget, signal, p, "", true, {gobject.ConnectFlag.after}) + +template connectSwapped*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped = + mconnect(widget, signal, p, arg, false, {gobject.ConnectFlag.swapped}) + +template connectSwapped*(widget: gobject.Object; signal: string; p: typed): untyped = + mconnect(widget, signal, p, "", true, {gobject.ConnectFlag.swapped}) -#var TimeoutID {.compileTime, global.}: int -# macro timeoutAdd*(interval: Natural; p: untyped; arg: typed): untyped = var TimeoutID {.compileTime, global.}: int inc(TimeoutID) @@ -251,8 +274,6 @@ $1($5) """ % [$procName, ats, $interval.toStrLit, $procNameCdecl, $arg] result = parseStmt(r1s & r2s) -#var IdleID {.compileTime, global.}: int -# macro idleAdd*(p: untyped; arg: typed): untyped = var IdleID {.compileTime, global.}: int inc(IdleID) @@ -281,4 +302,3 @@ proc $1(a: $2): culong {.discardable.} = $1($4) """ % [$procName, ats, $procNameCdecl, $arg] result = parseStmt(r1s & r2s) - diff --git a/tests/gen.nim b/tests/gen.nim index 8a142fa..871346e 100644 --- a/tests/gen.nim +++ b/tests/gen.nim @@ -1,5 +1,5 @@ # High level gobject-introspection based GTK3/GTK4 bindings for the Nim programming language -# v 0.5.3 2019-AUG-13 +# v 0.5.5 2019-SEP-21 # (c) S. Salewski 2018 # https://wiki.gnome.org/Projects/GObjectIntrospection @@ -1174,12 +1174,14 @@ template writeSignal() = xxx = xxx & " | " & provider.join(" | ") signalbuffer.write("proc " & mangleName("sc_" & $gBaseInfoGetName(signalInfo)) & EM & "(self: " & xxx & "; ") if gCallableInfoGetNArgs(signalInfo) > 0 or gTypeInfoGetTag(zzzu) != GITypeTag.VOID: - signalbuffer.writeLine(" p: proc (self: ptr " & yyy & "00; " & h & " {.cdecl.}, xdata: pointer = nil): culong =") + #signalbuffer.writeLine(" p: proc (self: ptr " & yyy & "00; " & h & " {.cdecl.}, xdata: pointer = nil, cf: gobject.ConnectFlags = {}): culong =") + signalbuffer.writeLine(" p: proc (self: ptr " & yyy & "00; " & h & " {.cdecl.}, xdata: pointer, cf: gobject.ConnectFlags): culong =") else: - signalbuffer.writeLine(" p: proc (self: ptr gobject.Object00; " & h & " {.cdecl.}, xdata: pointer = nil): culong =") + #signalbuffer.writeLine(" p: proc (self: ptr gobject.Object00; " & h & " {.cdecl.}, xdata: pointer = nil, cf: gobject.ConnectFlags = {}): culong =") + signalbuffer.writeLine(" p: proc (self: ptr gobject.Object00; " & h & " {.cdecl.}, xdata: pointer, cf: gobject.ConnectFlags): culong =") signalbuffer.write(" g_signal_connect_data(self.impl, \"") signalbuffer.write($gBaseInfoGetName(signalInfo)) - signalbuffer.writeLine("\", cast[GCallback](p), xdata, nil, cast[ConnectFlags](0))") + signalbuffer.writeLine("\", cast[GCallback](p), xdata, nil, cf)") proc writeInterface(info: GIInterfaceInfo) = if not suppressType: