From 5b20421fbafc837b2c35e6585554796a71fd2715 Mon Sep 17 00:00:00 2001 From: Stefan Salewski Date: Sat, 30 Sep 2017 23:44:20 +0200 Subject: [PATCH] fixes for listview support --- README.adoc | 131 ++++++++++++++++++++++++++++++++++++++++++ examples/listview.nim | 102 ++++++++++++++++++++++++++++++++ gintro/gimpl.nim | 4 +- tests/gen.nim | 115 ++++++++++++++++++++++-------------- 4 files changed, 307 insertions(+), 45 deletions(-) create mode 100644 examples/listview.nim diff --git a/README.adoc b/README.adoc index fd0e719..3792037 100644 --- a/README.adoc +++ b/README.adoc @@ -704,5 +704,136 @@ proc main = main() ---- +== A simple ListView example + +image::NimGTK3ListView.png[] + +Recently someone reported about some problems porting a GTK2 application to Nim GTK3, so I will give a small example +which may help using ListViews and TreeViews. These two widget types are the most complicated widget types in GTK -- +I can remember that I had some trouble myself when I used Ruby-GTK some years ago. As I can currently not remember +details about use of ListView widgets, I decided to take an example code from http://zetcode.com/gui/gtk2/gtktreeview/[zetcode.com] as starting point. Of course +porting is straight forward, but when I tried to compile the result I noticed some bugs and restrictions of current +gintro package. Of course not really surprising, as the package is not really tested yet. I will try to fix these bugs later. +First problem is, that we store a ListStore as model in our TreeView, and we need to extract that ListStore from the TreeView +for some operations. But module gtk.nim offers currently only a function to extract the model itself, which is of type TreeModel. +In the C code an upcast is used to get the ListStore from the retrieved TreeModel. To avoid casting in our Nim code, I have just copied +the getModel() proc and modified it to return a ListStore. Second problem was, that module gio export a ListStore datatype also. +To avoid prefixing all ListStore types with gtk prefix, I excluded gio.ListStore from import list. And finally a real bug: +Proc newListStore() expects currently a plain pointer as last parameter, while we know that it should be the address of a list of GTypes. +So we have to use an ugly cast for now. For populating the ListStore currently GValues are used. That is not very convenient, and +for that we need the correct GType of our string list. In C one would use the macro G_TYPE_STRING, which is not provided by +gobject-introspection. So we use typeFromName() to get the correct GType, which works fine when we know that the string name is "gchararray". +Later we will provide a higher level function for this process. + +I will try to give more and better explained ListView and TreeView examples later... + +[[listview.nim]] +[source,nim] +.listview.nim +---- +# http://zetcode.com/gui/gtk2/gtktreeview/ +# dynamiclistview.c + +import gintro/[glib, gobject, gtk] +import gintro/gio except ListStore + +const + LIST_ITEM = 0 + N_COLUMNS = 1 + +var list: TreeView + +# this is copied from gtk.nim +#proc getModel*(self: TreeView): TreeModel = +# new(result) +# result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl)) + +proc getListStore(self: TreeView): ListStore = + new(result) + result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl)) + +proc appendItem(widget: Button; entry: Entry) = + var + val: Value + iter: TreeIter + let store = getListStore(list) + let gtype = typeFromName("gchararray") + discard gValueInit(val, gtype) + gValueSetString(val, entry.text) + store.append(iter) + store.setValue(iter, LIST_ITEM, val) + entry.text = "" + +proc removeItem(widget: Button; selection: TreeSelection) = + var + ls: ListStore + iter: TreeIter + let store = getListStore(list) + if not store.getIterFirst(iter): + return + if getSelected(selection, ls, iter): + discard store.remove(iter) + +proc onRemoveAll(widget: Button; selection: TreeSelection) = + var + iter: TreeIter + let store = getListStore(list) + if not store.getIterFirst(iter): + return + clear(store) + +proc initList(list: TreeView) = + let renderer = newCellRendererText() + let column = newTreeViewColumn() + column.title = "List Item" + column.packStart(renderer, true) + column.addAttribute(renderer, "text", LIST_ITEM) + discard list.appendColumn(column) + let gtype = typeFromName("gchararray") + let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim + list.setModel(store) + +proc appActivate(app: Application) = + let + window = newApplicationWindow(app) + sw = newScrolledWindow() + hbox = newBox(Orientation.horizontal, 5) + vbox = newBox(Orientation.vertical, 0) + add = newButton("Add") + remove = newButton("Remove") + removeAll = newButton("Remove All") + entry = newEntry() + window. title = "List view" + window.position = WindowPosition.center + window.borderWidth = 10 + window.setSizeRequest(370, 270) + list = newTreeView() + sw.add(list) + sw.setPolicy(PolicyType.automatic, PolicyType.automatic) + sw.setShadowType(ShadowType.etchedIn) + list.setHeadersVisible(false) + vbox.packStart(sw, true, true, 5) + entry.setSizeRequest(120, -1) + hbox.packStart(add, false, true, 3) + hbox.packStart(entry, false, true, 3) + hbox.packStart(remove, false, true, 3) + hbox.packStart(removeAll, false, true, 3) + vbox.packStart(hbox, false, true, 3) + window.add(vbox) + initList(list) + let selection = getSelection(list) + connect(add, "clicked", listview.appendItem, entry) + connect(remove, "clicked", listview.removeItem, selection) + connect(removeAll, "clicked", listview.onRemoveAll, selection) + showAll(window) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", appActivate) + discard run(app) + +main() +---- + NOTE: Related work: https://github.com/jdmansour/nim-smartgi diff --git a/examples/listview.nim b/examples/listview.nim new file mode 100644 index 0000000..7b66db1 --- /dev/null +++ b/examples/listview.nim @@ -0,0 +1,102 @@ +# http://zetcode.com/gui/gtk2/gtktreeview/ +# dynamiclistview.c + +import gintro/[glib, gobject, gtk] +import gintro/gio except ListStore + +const + LIST_ITEM = 0 + N_COLUMNS = 1 + +var list: TreeView + +# this is copied from gtk.nim +#proc getModel*(self: TreeView): TreeModel = +# new(result) +# result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl)) + +proc getListStore(self: TreeView): ListStore = + new(result) + result.impl = gtk_tree_view_get_model(cast[ptr TreeView00](self.impl)) + +proc appendItem(widget: Button; entry: Entry) = + var + val: Value + iter: TreeIter + let store = getListStore(list) + let gtype = typeFromName("gchararray") + discard gValueInit(val, gtype) + gValueSetString(val, entry.text) + store.append(iter) + store.setValue(iter, LIST_ITEM, val) + entry.text = "" + +proc removeItem(widget: Button; selection: TreeSelection) = + var + ls: ListStore + iter: TreeIter + let store = getListStore(list) + if not store.getIterFirst(iter): + return + if getSelected(selection, ls, iter): + discard store.remove(iter) + +proc onRemoveAll(widget: Button; selection: TreeSelection) = + var + iter: TreeIter + let store = getListStore(list) + if not store.getIterFirst(iter): + return + clear(store) + +proc initList(list: TreeView) = + let renderer = newCellRendererText() + let column = newTreeViewColumn() + column.title = "List Item" + column.packStart(renderer, true) + column.addAttribute(renderer, "text", LIST_ITEM) + discard list.appendColumn(column) + let gtype = typeFromName("gchararray") + let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim + list.setModel(store) + +proc appActivate(app: Application) = + let + window = newApplicationWindow(app) + sw = newScrolledWindow() + hbox = newBox(Orientation.horizontal, 5) + vbox = newBox(Orientation.vertical, 0) + add = newButton("Add") + remove = newButton("Remove") + removeAll = newButton("Remove All") + entry = newEntry() + window. title = "List view" + window.position = WindowPosition.center + window.borderWidth = 10 + window.setSizeRequest(370, 270) + list = newTreeView() + sw.add(list) + sw.setPolicy(PolicyType.automatic, PolicyType.automatic) + sw.setShadowType(ShadowType.etchedIn) + list.setHeadersVisible(false) + vbox.packStart(sw, true, true, 5) + entry.setSizeRequest(120, -1) + hbox.packStart(add, false, true, 3) + hbox.packStart(entry, false, true, 3) + hbox.packStart(remove, false, true, 3) + hbox.packStart(removeAll, false, true, 3) + vbox.packStart(hbox, false, true, 3) + window.add(vbox) + initList(list) + let selection = getSelection(list) + connect(add, "clicked", listview.appendItem, entry) + connect(remove, "clicked", listview.removeItem, selection) + connect(removeAll, "clicked", listview.onRemoveAll, selection) + showAll(window) + +proc main = + let app = newApplication("org.gtk.example") + connect(app, "activate", appActivate) + discard run(app) + +main() diff --git a/gintro/gimpl.nim b/gintro/gimpl.nim index cfca9b5..4ef98d8 100644 --- a/gintro/gimpl.nim +++ b/gintro/gimpl.nim @@ -140,7 +140,7 @@ proc $1$2 {.cdecl.} = r1s.add(resu & "\n") all = all.replace(")", "; data: pointer)") r1s = r1s % [$procNameCdecl, all, $p, wts, ats] - echo r1s + #echo r1s result = parseStmt(r1s) if not ignoreArg.boolVal: ahl = ahl.replace(")", "; arg: " & ats & ")") @@ -170,7 +170,7 @@ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} = sc$5(self, $6, cast[pointer](ar[])) $1($7, $8, $9) """ % [$procName, wts, ahl, ats, signalName, $procNameCdecl, $(widget.toStrLit), $p, $(arg.toStrLit)] - echo r2s + #echo r2s result.add(parseStmt(r2s)) template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped = diff --git a/tests/gen.nim b/tests/gen.nim index 29bd592..68690c4 100644 --- a/tests/gen.nim +++ b/tests/gen.nim @@ -1,5 +1,5 @@ # High level gobject-introspection based GTK3 bindings for the Nim programming language -# v 0.2 2017-SEP-27 +# v 0.2 2017-SEP-30 # (c) S. Salewski 2017 # https://wiki.gnome.org/Projects/GObjectIntrospection @@ -127,9 +127,6 @@ var ct: CountTable[string] fixedProcNames.add("gtk_button_new_with_label", "newButton") fixedProcNames.add("pango_font_description_from_string", "newFontDescription") fixedProcNames.add("pango_language_from_string", "languageFromString") -#fixedProcNames.add("gdk_event_get", "getEvent") -#fixedProcNames.add("gdk_display_manager_get", "getDisplayManager") -#fixedProcNames.add("gdk_display_get_default", "getDefaultDisplay") defaultParameters.add("gtk_window_new", "`type` WindowType WindowType.toplevel") defaultParameters.add("gtk_application_new", "flags gio.ApplicationFlags {}") @@ -290,7 +287,7 @@ proc genRec(t: GITypeInfo; genProxy = false; fullQualified: bool = false): strin else: result = $gTypeTagToString(tag) result = mangleType(result) - if p and (not proxyResult):# or callerAlloc.contains(result)): + if p and (not proxyResult): result = "ptr " & result result = mangleType(result) & rawmark @@ -328,6 +325,7 @@ ct2nt.add("cstring", "string") ct2nt.add("gboolean", "bool") proc ct3nt(s: string): string = + if not ct2nt.contains(s): return s assert(ct2nt.contains(s)) result = case s: @@ -361,7 +359,6 @@ proc genP(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil): (str let provider = interfaceProvider[h] for i in provider: discard mangleType(i) h = h & " | " & provider.join(" | ") - if (sym.startsWith("gdk_events_get_") or sym.startsWith("gdk_event_get_")) and h == "Event": h = "SomeEvent" resul = "(self: " & h @@ -396,14 +393,19 @@ proc genP(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil): (str let provider = interfaceProvider[str] for i in provider: discard mangleType(i) str = str & " | " & provider.join(" | ") + if gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT: + str.insert("(") + str.add(")") + let name = mangleName(gBaseInfoGetName(arg)) if isProxyCandidate(t) and not gArgInfoIsCallerAllocates(arg) and not userAlloc: - ############## - if mayBeNil: - #arglist.add("if $1.isNil: nil else: cast[" & genRec(t, false) & "]($1.impl)" % name) + if mayBeNil and gArgInfoGetDirection(arg) != GIDirection.OUT: arglist.add("if " & name & ".isNil: nil else: cast[" & genRec(t, false) & "](" & name & ".impl)") else: - arglist.add("cast[" & genRec(t, false) & "](" & name & ".impl)") + if gArgInfoGetDirection(arg) == GIDirection.OUT: + arglist.add("cast[var " & genRec(t, false) & "](addr " & name & ".impl)") + else: + arglist.add("cast[" & genRec(t, false) & "](" & name & ".impl)") else: if ct2nt.contains(str) and (gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT): let kk = if name[0] == '`': name[1 .. ^2] else: name @@ -428,26 +430,23 @@ proc genP(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil): (str (h1, h2, h3) = defaultParameters[sym].split if name == h1 and str == h2: str.add(" = " & h3) - - ############################ if genProxy and str == "string" and mayBeNil: str.add(" = nil") - if genProxy and isProxyCandidate(t) and mayBeNil: - str.add(" = nil") - - # ------------------ + #if not str.contains("|") and (gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT): + # str.add(" = cast[" & str & "](nil)") + if gArgInfoGetDirection(arg) notin {GIDirection.OUT, GIDirection.INOUT}: + #if gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT: + # discard + #else: + str.add(" = nil") if (genProxy or sym == "pango_extents_to_pixels") and isFunctionInfo(info) and (gFunctionInfoGetFlags(info).int and GIFunctionInfoFlags.WRAPS_VFUNC.ord) == 0: if userAlloc and mayBeNil: - #str.add(" | ptr " & str & " = nil") var h = str if h.startsWith("var "): h = h[4 .. ^1] str.add(" = cast[ptr " & h & "](nil)[]") - # ------------------ - if (sym.startsWith("gdk_events_get_") or sym.startsWith("gdk_event_get_")) and str == "Event": str = "SomeEvent" - resul.add(name & ": " & str) if j < m: resul.add("; ") @@ -488,7 +487,6 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) = var replist: TableRef[string, string] let p = methodBuffer.getPosition let sym = $gFunctionInfoGetSymbol(mInfo) - if sym[^1] == '_': return if sym.contains("__"): return if processedFunctions.contains(sym): return @@ -498,11 +496,9 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) = for j in 0 ..< gCallableInfoGetNArgs(minfo): let arg = gCallableInfoGetArg(minfo, j) let t = gArgInfoGetType(arg) - if isProxyCandidate(t) and gArgInfoGetDirection(arg) == GIDirection.OUT and not gArgInfoIsCallerAllocates(arg): return if sym == "g_iconv": return try: - #if sym == "g_value_get_string": echo "g_value_get_string"; quit() (plist, arglist, replist) = genP(mInfo, genProxy, info) methodBuffer.write("\nproc " & sym & EM & plist) methodBuffer.writeLine(" {.\n importc: \"", sym, "\", ", libprag, ".}") @@ -515,8 +511,7 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) = asym.add($manglename(gBaseInfoGetName(info))) if keywords.split.contains(asym) or nims.split.contains(asym): asym.add('P') var ret2 = gCallableInfoGetReturnType(minfo) - #if sym == "g_value_get_string": echo isString(ret2); quit() - var ret22 = gCallableInfoGetReturnType(minfo) + #var ret22 = gCallableInfoGetReturnType(minfo) for run in 0 .. 1: if run == 1: if fixedProcNames.contains(sym): continue @@ -557,6 +552,16 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) = hhh = hhh.replace("new", "init") hhh = hhh.replace("*(", "*[T](result: var T" & semi) methodBuffer.writeLine(hhh & " =") + + if true:#needProxyProc(mInfo): + for j in 0 ..< gCallableInfoGetNArgs(minfo): + let arg = gCallableInfoGetArg(minfo, j) + let t = gArgInfoGetType(arg) + if gArgInfoGetDirection(arg) == GIDirection.OUT:# and not callerAlloc.contains(genRec(t, true, true)): #and not gArgInfoIsCallerAllocates(arg) + assert(false) + let h2 = mangleName(gBaseInfoGetName(arg)) + methodBuffer.writeLine(" if " & h2 & ".isNil: " & h2 & " = new type(" & h2 & ")") + if gCallableInfoCanThrowGerror(minfo): methodBuffer.writeLine(" var gerror: ptr Error00") if i == 0: arglist.insert(", addr gerror", arglist.high) @@ -682,7 +687,6 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) = methodBuffer.writeLine(" new(result, gobject.genericGObjectUnref)") methodBuffer.writeLine(" result.impl = " & sym & arglist) else: - #if gBaseInfoGetType(iface) == GIInfoType.Struct: let nMethods = gStructInfoGetNMethods(iface) var freeMeName: string for j in 0 .. 0 or gTypeInfoGetTag(zzzu) != GITypeTag.VOID: signalbuffer.writeLine(" p: proc (self: ptr " & yyy & "00; " & h & " {.cdecl.}, xdata: pointer = nil): culong =") @@ -900,9 +934,6 @@ proc writeInterface(info: GIInterfaceInfo) = signalbuffer.write(" g_signal_connect_data(self.impl, \"") signalbuffer.write($gBaseInfoGetName(signalInfo)) signalbuffer.writeLine("\", cast[GCallback](p), xdata, nil, cast[ConnectFlags](0))") - - ################################ - let nMethods = gInterfaceInfoGetNMethods(info) for j in 0 ..