cleanup and fixes for libnice support
This commit is contained in:
parent
2995abfd57
commit
8966bd9ea1
13 changed files with 1252 additions and 449 deletions
52
README.adoc
52
README.adoc
|
|
@ -1,6 +1,6 @@
|
||||||
= High level GTK3 (and GTK4) bindings for the Nim programming language
|
= High level GTK3 (and GTK4) bindings for the Nim programming language
|
||||||
(c) Stefan Salewski
|
(c) Stefan Salewski
|
||||||
//Version 0.8.2
|
//Version 0.8.3
|
||||||
:experimental:
|
:experimental:
|
||||||
:imagesdir: http://ssalewski.de/tmp
|
:imagesdir: http://ssalewski.de/tmp
|
||||||
:source-highlighter: pygments
|
:source-highlighter: pygments
|
||||||
|
|
@ -25,6 +25,56 @@ with code inserted from files directly.
|
||||||
NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf, E. Bassi and other _Nim_ and _GTK/Gnome_ developers.
|
NOTE: This work is partly based on earlier works of J. Mansour and has been supported by A. Rumpf, E. Bassi and other _Nim_ and _GTK/Gnome_ developers.
|
||||||
The `combinatorics` module was kindly provided by R. Behrends.
|
The `combinatorics` module was kindly provided by R. Behrends.
|
||||||
|
|
||||||
|
NOTE: We are currently preparing v0.8.3. You may install it with command [black yellow-background]#nimble install gintro@#head#.
|
||||||
|
This version will have some internal cleanup, which removes the temporary
|
||||||
|
Array types. In the past we used that names to indicate array parameters, it was working well, but still
|
||||||
|
it was ugly using names to indicates data types. So we fixed that. And we have added some better support for
|
||||||
|
GList parameter. GLists are now converted to Nim seqs and vice versa. Not everywhere still, and this conversion
|
||||||
|
is still untested, as GList parameters are used most of the time in exotic functions only. Another change is that
|
||||||
|
we have support for libnice by a request of a user from Japan. We added the sdp_example.nim converted from
|
||||||
|
a C example, it seems to compile and run. And finally with v0.8.3 the examples from the GTK4 book should work.
|
||||||
|
For the GTK3 examples we had to do some minimal fixes, so you may have to do similar fixes for your own code as well.
|
||||||
|
For gintro/examples/gtk3 this was
|
||||||
|
|
||||||
|
----
|
||||||
|
# examples modifications for v0.8.3:
|
||||||
|
$ diff gintronew/examples/gtk3 gintro/examples/gtk3
|
||||||
|
diff gintronew/examples/gtk3/celldatafunction.nim gintro/examples/gtk3/celldatafunction.nim
|
||||||
|
3c3
|
||||||
|
< import gintro/[gtk, gobject, glib]
|
||||||
|
---
|
||||||
|
> import gintro/[gtk, gobject]
|
||||||
|
67c67
|
||||||
|
< let store = newListStore(Columns, cast[ptr GType](unsafeaddr gtype)) # ugly cast
|
||||||
|
---
|
||||||
|
> let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast
|
||||||
|
diff gintronew/examples/gtk3/css_colored_listview.nim gintro/examples/gtk3/css_colored_listview.nim
|
||||||
|
64c64
|
||||||
|
< var store = newListStore(Columns, cast[ptr GType]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
|
---
|
||||||
|
> var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
|
diff gintronew/examples/gtk3/gearsmenu.nim gintro/examples/gtk3/gearsmenu.nim
|
||||||
|
114c114
|
||||||
|
< echo fmt"GTK Version {getMajorVersion()}.{getMinorVersion()}.{getMicroVersion()}"
|
||||||
|
---
|
||||||
|
> echo fmt"GTK Version {majorVersion()}.{minorVersion()}.{microVersion()}"
|
||||||
|
diff gintronew/examples/gtk3/listview.nim gintro/examples/gtk3/listview.nim
|
||||||
|
61c61
|
||||||
|
< let store = newListStore(N_COLUMNS, cast[ptr GType]( unsafeaddr gtype)) # cast due to bug in gtk.nim
|
||||||
|
---
|
||||||
|
> let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim
|
||||||
|
diff gintronew/examples/gtk3/menubar.nim gintro/examples/gtk3/menubar.nim
|
||||||
|
101c101
|
||||||
|
< echo "GTK Version $1.$2.$3" % [$getMajorVersion(), $getMinorVersion(), $getMicroVersion()]
|
||||||
|
---
|
||||||
|
> echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()]
|
||||||
|
diff gintronew/examples/gtk3/overlay_tree1.nim gintro/examples/gtk3/overlay_tree1.nim
|
||||||
|
71c71
|
||||||
|
< let store = newListStore(Columns, cast[ptr GType](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
|
---
|
||||||
|
> let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
|
----
|
||||||
|
|
||||||
NOTE: This is version 0.8.2 of gintro. It is only a fix for recent gstreamer 1.18 which added some uncommon
|
NOTE: This is version 0.8.2 of gintro. It is only a fix for recent gstreamer 1.18 which added some uncommon
|
||||||
gobject-introspection stuff which brook the install of 0.8.1 for a few users. We where not yet able to
|
gobject-introspection stuff which brook the install of 0.8.1 for a few users. We where not yet able to
|
||||||
verify that the gst example which is bases on the gst C tutorial1 works with gstreamer 1.18.
|
verify that the gst example which is bases on the gst C tutorial1 works with gstreamer 1.18.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# This example shows how to apply a CellDataFunc to a GtkTreeView
|
# 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
|
# C example code was provided by A.Krause in chapter 8 of his book
|
||||||
import gintro/[gtk, gobject]
|
import gintro/[gtk, gobject, glib]
|
||||||
|
|
||||||
const
|
const
|
||||||
Color = 0
|
Color = 0
|
||||||
|
|
@ -64,7 +64,7 @@ proc main =
|
||||||
let treeview = newTreeView()
|
let treeview = newTreeView()
|
||||||
setupTreeView(treeview)
|
setupTreeView(treeview)
|
||||||
let gtype = typeFromName("gchararray")
|
let gtype = typeFromName("gchararray")
|
||||||
let store = newListStore(Columns, cast[pointer](unsafeaddr gtype)) # ugly cast
|
let store = newListStore(Columns, cast[ptr GType](unsafeaddr gtype)) # ugly cast
|
||||||
## Add all of the products to the GtkListStore.
|
## Add all of the products to the GtkListStore.
|
||||||
for i in 0 ..< 6:
|
for i in 0 ..< 6:
|
||||||
for j in 0 ..< 6:
|
for j in 0 ..< 6:
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ proc main() =
|
||||||
var iter: TreeIter
|
var iter: TreeIter
|
||||||
var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
|
var h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
|
||||||
typeFromName("gchararray")]
|
typeFromName("gchararray")]
|
||||||
var store = newListStore(Columns, cast[pointer]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
var store = newListStore(Columns, cast[ptr GType]( unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
||||||
for i, n in progNames:
|
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.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ proc main =
|
||||||
let app = newApplication("app.example")
|
let app = newApplication("app.example")
|
||||||
connect(app, "startup", appStartup)
|
connect(app, "startup", appStartup)
|
||||||
connect(app, "activate", appActivate)
|
connect(app, "activate", appActivate)
|
||||||
echo fmt"GTK Version {majorVersion()}.{minorVersion()}.{microVersion()}"
|
echo fmt"GTK Version {getMajorVersion()}.{getMinorVersion()}.{getMicroVersion()}"
|
||||||
let status = run(app)
|
let status = run(app)
|
||||||
quit(status)
|
quit(status)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ proc initList(list: TreeView) =
|
||||||
column.addAttribute(renderer, "text", LIST_ITEM)
|
column.addAttribute(renderer, "text", LIST_ITEM)
|
||||||
discard list.appendColumn(column)
|
discard list.appendColumn(column)
|
||||||
let gtype = gStringGetType() # typeFromName("gchararray")
|
let gtype = gStringGetType() # typeFromName("gchararray")
|
||||||
let store = newListStore(N_COLUMNS, cast[pointer]( unsafeaddr gtype)) # cast due to bug in gtk.nim
|
let store = newListStore(N_COLUMNS, cast[ptr GType]( unsafeaddr gtype)) # cast due to bug in gtk.nim
|
||||||
list.setModel(store)
|
list.setModel(store)
|
||||||
|
|
||||||
proc appActivate(app: Application) =
|
proc appActivate(app: Application) =
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ proc main =
|
||||||
let app = newApplication("app.example")
|
let app = newApplication("app.example")
|
||||||
connect(app, "startup", appStartup)
|
connect(app, "startup", appStartup)
|
||||||
connect(app, "activate", appActivate)
|
connect(app, "activate", appActivate)
|
||||||
echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()]
|
echo "GTK Version $1.$2.$3" % [$getMajorVersion(), $getMinorVersion(), $getMicroVersion()]
|
||||||
let status = run(app)
|
let status = run(app)
|
||||||
quit(status)
|
quit(status)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ proc main =
|
||||||
var iter: TreeIter
|
var iter: TreeIter
|
||||||
let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
|
let h = [typeFromName("guint"), typeFromName("gchararray"), typeFromName("gchararray"),
|
||||||
typeFromName("gchararray")]
|
typeFromName("gchararray")]
|
||||||
let store = newListStore(Columns, cast[pointer](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
let store = newListStore(Columns, cast[ptr GType](unsafeaddr h)) # cast is ugly, we should fix it in bindings.
|
||||||
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
let progNames = ["Gedit", "Gimp", "Inkscape", "Firefox", "Calculator", "Devhelp"]
|
||||||
for i, n in progNames:
|
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.append(iter) # currently we have to use setValue() as there is no varargs proc as in C original
|
||||||
|
|
|
||||||
219
examples/gtk3/sdp_example.nim
Normal file
219
examples/gtk3/sdp_example.nim
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
# https://gitlab.freedesktop.org/libnice/libnice/-/blob/master/examples/sdp-example.c
|
||||||
|
# nim c --gc:arc sdp_example.nim
|
||||||
|
#
|
||||||
|
# Example using libnice to negotiate a UDP connection between two clients,
|
||||||
|
# possibly on the same network or behind different NATs and/or stateful
|
||||||
|
# firewalls.
|
||||||
|
#
|
||||||
|
# Run two clients, one controlling and one controlled:
|
||||||
|
# ./sdp-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
|
||||||
|
# ./sdp-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
|
||||||
|
|
||||||
|
import gintro/[gtk, glib, gobject, nice]
|
||||||
|
import gintro/gio except stdin
|
||||||
|
from strutils import `%`
|
||||||
|
from os import paramCount, paramStr
|
||||||
|
from strutils import parseInt
|
||||||
|
|
||||||
|
var
|
||||||
|
gloop: glib.MainLoop
|
||||||
|
stunAddr: string
|
||||||
|
stunPort: int
|
||||||
|
controlling: int
|
||||||
|
exitThread: bool
|
||||||
|
candidateGatheringDone: bool
|
||||||
|
negotiationDone: bool
|
||||||
|
gatherMutex: glib.Mutex
|
||||||
|
negotiateMutex: glib.Mutex
|
||||||
|
gatherCond: glib.Cond
|
||||||
|
negotiateCond: glib.Cond
|
||||||
|
stateName = ["disconnected", "gathering", "connecting", "connected", "ready", "failed"]
|
||||||
|
|
||||||
|
proc gDebug(s: string) =
|
||||||
|
stdout.write(s)
|
||||||
|
flushFile(stdout)
|
||||||
|
|
||||||
|
proc gError(s: string) =
|
||||||
|
stderr.write(s)
|
||||||
|
flushFile(stderr)
|
||||||
|
|
||||||
|
proc toStringVal(s: string): Value =
|
||||||
|
let gtype = gStringGetType()
|
||||||
|
discard init(result, gtype)
|
||||||
|
setString(result, s)
|
||||||
|
|
||||||
|
proc toBoolVal(b: bool): Value =
|
||||||
|
let gtype = gBooleanGetType()
|
||||||
|
discard init(result, gtype)
|
||||||
|
setBoolean(result, b)
|
||||||
|
|
||||||
|
proc toIntVal(i: int): Value =
|
||||||
|
let gtype = typeFromName("gint")
|
||||||
|
discard init(result, gtype)
|
||||||
|
setInt(result, i)
|
||||||
|
|
||||||
|
proc cbCandidateGatheringDone(agent: nice.Agent; streamID: int) =
|
||||||
|
gDebug("SIGNAL candidate gathering done\n")
|
||||||
|
glib.lock(gatherMutex)
|
||||||
|
candidateGatheringDone = true
|
||||||
|
glib.signal(gatherCond)
|
||||||
|
glib.unlock(gatherMutex)
|
||||||
|
|
||||||
|
proc cbComponentStateChanged(agent: nice.Agent; streamID: int; componentID: int; state: int) =
|
||||||
|
gDebug("SIGNAL: state changed $1 $2 $3[$4]\n" % [$streamID, $componentID, stateName[state.ord], $state])
|
||||||
|
if state == nice.ComponentState.ready.ord:
|
||||||
|
glib.lock(negotiateMutex)
|
||||||
|
negotiationDone = true
|
||||||
|
glib.signal(negotiateCond)
|
||||||
|
glib.unlock(negotiateMutex)
|
||||||
|
elif state == nice.ComponentState.failed.ord:
|
||||||
|
glib.quit(gloop)
|
||||||
|
|
||||||
|
# proc nice_agent_attach_recv*(self: ptr Agent00; streamID: cuint; componentID: cuint; ctx: ptr glib.MainContext;
|
||||||
|
# fn: AgentRecvFunc; data: pointer): gboolean {.importc, libprag.}
|
||||||
|
#
|
||||||
|
# this has to be a low level function!
|
||||||
|
# AgentRecvFunc* = proc (agent: ptr Agent00; streamId: uint32; componentId: uint32; len: uint32;
|
||||||
|
# buf: cstring; userData: pointer) {.cdecl.}
|
||||||
|
proc cbNiceRecv(agent: ptr nice.Agent00; streamID: uint32; componentID: uint32; len: uint32; buf: cstring; userData: pointer) {.cdecl.} =
|
||||||
|
if len == 1 and buf[0] == '\x00':
|
||||||
|
glib.quit(gloop)
|
||||||
|
discard stdout.writeBuffer(buf, len)
|
||||||
|
flushfile(stdout)
|
||||||
|
|
||||||
|
proc exampleThread(data: pointer): pointer {.cdecl.} =
|
||||||
|
var
|
||||||
|
agent: nice.Agent
|
||||||
|
ioStdin: glib.IOChannel
|
||||||
|
streamID: int
|
||||||
|
line: string
|
||||||
|
sdp: string
|
||||||
|
sdp64: string
|
||||||
|
#when defined(G_OS_WIN32):
|
||||||
|
# io_stdin = g_io_channel_win32_new_fd(_fileno(stdin))
|
||||||
|
#else:
|
||||||
|
ioStdin = unixNew(getFileHandle(stdin))
|
||||||
|
discard setFlags(ioStdin, glib.IOFlags.nonBlock)
|
||||||
|
# Create the nice agent
|
||||||
|
agent = nice.newAgent(glib.getContext(gloop), nice.CompatibilityRfc5245)
|
||||||
|
if agent == nil:
|
||||||
|
gError("Failed to create agent")
|
||||||
|
# Set the STUN settings and controlling mode
|
||||||
|
if stunAddr.len > 0:
|
||||||
|
setProperty(agent, "stun-server", toStringVal(stunAddr))
|
||||||
|
setProperty(agent, "stun-server-port", toIntVal(stunPort))
|
||||||
|
setProperty(agent, "controlling-mode", toIntVal(controlling))
|
||||||
|
# Connect to the signals
|
||||||
|
agent.connect("candidate-gathering-done", cbCandidateGatheringDone)
|
||||||
|
agent.connect("component-state-changed", cbComponentStateChanged)
|
||||||
|
# Create a new stream with one component
|
||||||
|
streamID = addStream(agent, 1)
|
||||||
|
if streamID == 0:
|
||||||
|
gError("Failed to add stream")
|
||||||
|
discard setStreamName(agent, streamID, "text")
|
||||||
|
# Attach to the component to receive the data
|
||||||
|
# Without this call, candidates cannot be gathered
|
||||||
|
discard attachRecv(agent, streamID, 1, getContext(gloop), cbNiceRecv, nil)
|
||||||
|
# Start gathering local candidates
|
||||||
|
if not gatherCandidates(agent, stream_id):
|
||||||
|
gError("Failed to start candidate gathering")
|
||||||
|
gDebug("waiting for candidate-gathering-done signal...")
|
||||||
|
lock(gatherMutex)
|
||||||
|
while not exitThread and not candidateGatheringDone:
|
||||||
|
wait(gatherCond, gatherMutex)
|
||||||
|
unlock(gatherMutex)
|
||||||
|
block gotoEnd:
|
||||||
|
if exitThread:
|
||||||
|
break gotoEnd
|
||||||
|
# Candidate gathering is done. Send our local candidates on stdout
|
||||||
|
sdp = generateLocalSdp(agent)
|
||||||
|
stdout.write("Generated SDP from agent :\n$1\n\n" % [sdp])
|
||||||
|
stdout.write("Copy the following line to remote client:\n")
|
||||||
|
sdp64 = base64Encode(sdp)
|
||||||
|
stdout.write("\n $1\n", sdp64)
|
||||||
|
# Listen on stdin for the remote candidate list
|
||||||
|
stdout.write("Enter remote data (single line, no wrapping):\n")
|
||||||
|
stdout.write("> ")
|
||||||
|
flushFile(stdout)
|
||||||
|
while not exitThread:
|
||||||
|
var terminatorPos: uint64
|
||||||
|
var length: uint64
|
||||||
|
var s: IOStatus = readLine(ioStdin, line, length, terminatorPos)
|
||||||
|
if s == IOStatus.normal:
|
||||||
|
var sdpLen: uint64
|
||||||
|
let sdpxxx = base64Decode(line, sdpLen) #seq[uint8]
|
||||||
|
sdp = cast[string](sdpxxx)
|
||||||
|
# Parse remote candidate list and set it on the agent
|
||||||
|
if sdp.len > 0 and parseRemoteSdp(agent, sdp) > 0:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
write(stderr, "ERROR: failed to parse remote data\n")
|
||||||
|
write(stdout, "Enter remote data (single line, no wrapping):\n")
|
||||||
|
stdout.write("> ")
|
||||||
|
flushFile(stdout)
|
||||||
|
elif s == IOStatus.again:
|
||||||
|
usleep(100000)
|
||||||
|
gDebug("waiting for state READY or FAILED signal...")
|
||||||
|
lock(negotiateMutex)
|
||||||
|
while not exitThread and not negotiationDone:
|
||||||
|
wait(negotiateCond, negotiateMutex)
|
||||||
|
unlock(negotiateMutex)
|
||||||
|
if exitThread:
|
||||||
|
break gotoEnd
|
||||||
|
# Listen to stdin and send data written to it
|
||||||
|
stdout.write("\nSend lines to remote (Ctrl-D to quit):\n")
|
||||||
|
stdout.write("> ")
|
||||||
|
flushFile(stdout)
|
||||||
|
while not exitThread:
|
||||||
|
var terminatorPos: uint64
|
||||||
|
var length: uint64
|
||||||
|
var s: IOStatus = readLine(ioStdin, line, length, terminatorPos);
|
||||||
|
if s == IOStatus.normal:
|
||||||
|
discard send(agent, streamID, 1, line.len, line)
|
||||||
|
stdout.write("> ")
|
||||||
|
flushFile(stdout)
|
||||||
|
elif s == IOStatus.again:
|
||||||
|
usleep(100000)
|
||||||
|
else:
|
||||||
|
# Ctrl-D was pressed.
|
||||||
|
discard send(agent, stream_id, 1, 1, "\x00")
|
||||||
|
break
|
||||||
|
# END label
|
||||||
|
quit(gloop)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
echo paramCount()
|
||||||
|
for i in 0 .. paramCount():
|
||||||
|
echo paramStr(i)
|
||||||
|
let argc = paramCount() + 1
|
||||||
|
var argv = newSeq[string](argc)
|
||||||
|
for i in 0 ..< argc:
|
||||||
|
argv[i] = paramStr(i)
|
||||||
|
var gexamplethread: glib.Thread
|
||||||
|
# Parse arguments
|
||||||
|
if argc > 4 or argc < 2 or argv[1].len > 1: # argv[1][1] != '\x00':
|
||||||
|
write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]])
|
||||||
|
quit(QuitFailure)
|
||||||
|
controlling = argv[1][0].ord - '0'.ord
|
||||||
|
if controlling != 0 and controlling != 1:
|
||||||
|
write(stderr, "Usage: $1 0|1 stun_addr [stun_port]\n" % [argv[0]])
|
||||||
|
quit(QuitFailure)
|
||||||
|
if argc > 2:
|
||||||
|
stun_addr = argv[2]
|
||||||
|
if argc > 3:
|
||||||
|
stun_port = parseInt(argv[3])
|
||||||
|
else:
|
||||||
|
stun_port = 3478
|
||||||
|
gDebug("Using stun server \'[$1]:$2\'\n" % [$stunAddr, $stunPort])
|
||||||
|
gio.networkingInit()
|
||||||
|
gloop = newMainLoop(nil, false)
|
||||||
|
# Run the mainloop and the example thread
|
||||||
|
exitThread = false
|
||||||
|
gexamplethread = newThread("example thread", exampleThread, nil)
|
||||||
|
run(gloop)
|
||||||
|
exitThread = true
|
||||||
|
discard join(gexamplethread)
|
||||||
|
quit(QuitSuccess)
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -117,7 +117,7 @@ proc main =
|
||||||
content.setVexpand
|
content.setVexpand
|
||||||
box.append(content)
|
box.append(content)
|
||||||
let footer = newActionBar()
|
let footer = newActionBar()
|
||||||
footer.setCenterWidget(newCheckButtonWithLabel("Middle"))
|
footer.setCenterWidget(newCheckButton("Middle"))
|
||||||
let button = newToggleButtonWithLabel("Custom")
|
let button = newToggleButtonWithLabel("Custom")
|
||||||
button.connect("clicked", changeHeader, window)
|
button.connect("clicked", changeHeader, window)
|
||||||
footer.packStart(button)
|
footer.packStart(button)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
author = "Stefan Salewski"
|
author = "Stefan Salewski"
|
||||||
description = "High level GObject-Introspection based GTK3/GTK4 bindings"
|
description = "High level GObject-Introspection based GTK3/GTK4 bindings"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from sequtils import keepItIf
|
#from sequtils import keepItIf
|
||||||
|
import sequtils
|
||||||
const RecSep = "!" # Record separator for entries in gisup.nim, also defined in gen.nim
|
const RecSep = "!" # Record separator for entries in gisup.nim, also defined in gen.nim
|
||||||
|
|
||||||
# caution: $$ due to string interpolation
|
# caution: $$ due to string interpolation
|
||||||
|
|
@ -188,6 +189,33 @@ proc $1$2 {.cdecl.} =
|
||||||
|
|
||||||
r1s.add(resu & "\n")
|
r1s.add(resu & "\n")
|
||||||
all = all.replace(")", "; user_data: pointer)")
|
all = all.replace(")", "; user_data: pointer)")
|
||||||
|
|
||||||
|
#echo all
|
||||||
|
|
||||||
|
if all.find("00Array") >= 0:
|
||||||
|
var h = all.split(';')
|
||||||
|
for el in mitems(h):
|
||||||
|
var a, b: string
|
||||||
|
(a, b) = el.split(": ")
|
||||||
|
if b.find("Array") >= 0:
|
||||||
|
b = "ptr " & b.replace("Array")
|
||||||
|
el = a & ": " & b
|
||||||
|
all = h.join(";")
|
||||||
|
|
||||||
|
#echo all
|
||||||
|
|
||||||
|
# https://forum.nim-lang.org/t/6775#42155
|
||||||
|
const matcher = "Array" # is this good enough?
|
||||||
|
var ra: string
|
||||||
|
for (elem, isSep) in all.tokenize(Whitespace + {',', ';'}):
|
||||||
|
if elem.endsWith(matcher):
|
||||||
|
ra.add("ptr " & elem[0 .. ^(matcher.len + 1)])
|
||||||
|
else:
|
||||||
|
ra.add(elem)
|
||||||
|
|
||||||
|
#echo ra
|
||||||
|
#assert ra == all
|
||||||
|
|
||||||
r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
|
r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
|
||||||
#echo r1s
|
#echo r1s
|
||||||
|
|
||||||
|
|
@ -449,4 +477,4 @@ $1(column, renderer, $6)
|
||||||
""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy]
|
""" % [$procName, ats, $column, $renderer, $procNameCdecl, argStr, $procNameDestroy]
|
||||||
|
|
||||||
result = parseStmt(r0s & r1s & r2s)
|
result = parseStmt(r0s & r1s & r2s)
|
||||||
# 429 lines
|
# 429 lines echo
|
||||||
|
|
|
||||||
1020
tests/gen.nim
1020
tests/gen.nim
File diff suppressed because it is too large
Load diff
292
tests/maxby.nim
Normal file
292
tests/maxby.nim
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
# https://forum.nim-lang.org/t/4763
|
||||||
|
|
||||||
|
proc maxIndexBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): int =
|
||||||
|
## Returns the index of the maximum value of all elements in s.
|
||||||
|
## Comparison is done by cmp() proc.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 1
|
||||||
|
if s.len > 0:
|
||||||
|
var maxVal = s[0]
|
||||||
|
for i in 1 .. s.high:
|
||||||
|
if cmp(s[i], maxVal) > 0:
|
||||||
|
result = i
|
||||||
|
maxVal = s[i]
|
||||||
|
|
||||||
|
proc minIndexBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): int =
|
||||||
|
## Returns the index of the minimum value of all elements in s.
|
||||||
|
## Comparison is done by cmp() proc.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 3
|
||||||
|
if s.len > 0:
|
||||||
|
var minVal = s[0]
|
||||||
|
for i in 1 .. s.high:
|
||||||
|
if cmp(s[i], minVal) < 0:
|
||||||
|
result = i
|
||||||
|
minVal = s[i]
|
||||||
|
|
||||||
|
# see system.sortedByIt()
|
||||||
|
template maxIndexByIt*(seq1, op: untyped): untyped =
|
||||||
|
## Returns the maximum index of all elements in s.
|
||||||
|
## Comparison is done by it expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxIndexByIt(it.len) == 1
|
||||||
|
var result = maxIndexBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var it {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
it = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template minIndexByIt*(seq1, op: untyped): untyped =
|
||||||
|
## Returns the minimum index of all elements in s.
|
||||||
|
## Comparison is done by it expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minIndexByIt(it.len) == 3
|
||||||
|
var result = minIndexBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var it {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
it = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template maxIndex*(seq1, el, op: untyped): untyped =
|
||||||
|
## Returns the maximum index of all elements in s.
|
||||||
|
## Comparison is done by custom expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxIndex(el, el.len) == 1
|
||||||
|
var result = maxIndexBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var el {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
el = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template minIndex*(seq1, el, op: untyped): untyped =
|
||||||
|
## Returns the minimum index of all elements in s.
|
||||||
|
## Comparison is done by custom expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minIndex(el, el.len) == 3
|
||||||
|
var result = minIndexBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var el {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
el = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
func maxValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): T =
|
||||||
|
## Returns the maximum value of all elements in s.
|
||||||
|
## Comparison is done by cmp() proc.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "Nim"
|
||||||
|
assert(s.len > 0)
|
||||||
|
result = s[0]
|
||||||
|
if s.len > 0:
|
||||||
|
for i in 1 .. s.high:
|
||||||
|
if cmp(s[i], result) > 0:
|
||||||
|
result = s[i]
|
||||||
|
|
||||||
|
func minValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): T =
|
||||||
|
## Returns the minimum value of all elements in s.
|
||||||
|
## Comparison is done by cmp() proc.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "D"
|
||||||
|
assert(s.len > 0)
|
||||||
|
result = s[0]
|
||||||
|
if s.len > 0:
|
||||||
|
for i in 1 .. s.high:
|
||||||
|
if cmp(s[i], result) < 0:
|
||||||
|
result = s[i]
|
||||||
|
|
||||||
|
template maxValueByIt*(seq1, op: untyped): untyped =
|
||||||
|
## Returns the maximum value of all elements in s.
|
||||||
|
## Comparison is done by it expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxValueByIt(it.len) == "Nim"
|
||||||
|
var result = maxValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var it {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
it = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template minValueByIt*(seq1, op: untyped): untyped =
|
||||||
|
## Returns the minimum value of all elements in s.
|
||||||
|
## Comparison is done by it expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minValueByIt(it.len) == "D"
|
||||||
|
var result = minValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var it {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
it = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template maxValue*(seq1, el, op: untyped): untyped =
|
||||||
|
## Returns the maximum value of all elements in s.
|
||||||
|
## Comparison is done by custom expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].maxValue(el, el.len) == "Nim"
|
||||||
|
var result = maxValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var el {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
el = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template minValue*(seq1, el, op: untyped): untyped =
|
||||||
|
## Returns the minimum value of all elements in s.
|
||||||
|
## Comparison is done by custom expression
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minValue(el, el.len) == "D"
|
||||||
|
var result = minValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var el {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
el = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
func minMaxValueBy*[T](s: openArray[T], cmp: proc (x, y: T): int {.closure.}): (T, T) =
|
||||||
|
## Returns the minimum and maximum values of all elements in s.
|
||||||
|
## Comparison is done by cmp() proc.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minMaxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == ("D", "Nim")
|
||||||
|
assert(s.len > 0)
|
||||||
|
if s.len > 0:
|
||||||
|
result[0] = s[0]
|
||||||
|
result[1] = s[0]
|
||||||
|
for i in 1 .. s.high:
|
||||||
|
if cmp(result[1], s[i]) < 0:
|
||||||
|
result[1] = s[i]
|
||||||
|
elif cmp(result[0], s[i]) > 0:
|
||||||
|
result[0] = s[i]
|
||||||
|
|
||||||
|
template minMaxValueByIt*(seq1, op: untyped): untyped =
|
||||||
|
## Returns the minimum and maximum values of all elements in s.
|
||||||
|
## Comparison is done by it expression.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minMaxValueByIt(it.len) == ("D", "Nim")
|
||||||
|
var result = minMaxValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var it {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
it = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
template minMaxValue*(seq1, el, op: untyped): untyped =
|
||||||
|
## Returns the minimum and maximum values of all elements in s.
|
||||||
|
## Comparison is done by custom expression.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:
|
||||||
|
## assert ["Go", "Nim", "C++", "D"].minMaxValue(el, el.len) == ("D", "Nim")
|
||||||
|
var result = minMaxValueBy(seq1, proc(x, y: type(seq1[0])): int =
|
||||||
|
var el {.inject.} = x
|
||||||
|
let a = op
|
||||||
|
el = y
|
||||||
|
let b = op
|
||||||
|
result = cmp(a, b))
|
||||||
|
result
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
|
||||||
|
type
|
||||||
|
Person = object
|
||||||
|
name: string
|
||||||
|
age: int
|
||||||
|
|
||||||
|
var people: seq[Person] = @[Person(name: "Eve", age: 11), Person(name: "Sarah", age: 25), Person(name: "Anna", age: 23)]
|
||||||
|
|
||||||
|
var (min, max) = people.minMaxValue(p, p.name)
|
||||||
|
|
||||||
|
echo "Min: ", min.name, ", ", min.age
|
||||||
|
echo "Max: ", max.name, ", ", max.age
|
||||||
|
|
||||||
|
var m = people.maxValueByIt(it.name.len)
|
||||||
|
|
||||||
|
echo "Max by length of name: ", m.name
|
||||||
|
|
||||||
|
var h = people.minIndexBy(proc (x,y: Person): int = cmp(x.name.len, y.name.len))
|
||||||
|
|
||||||
|
echo "Min index by length of name: ", h
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 1
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minIndexBy(proc (x,y: string): int = cmp(x.len, y.len)) == 3
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxIndexByIt(it.len) == 1
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minIndexByIt(it.len) == 3
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxIndex(el, el.len) == 1
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minIndex(el, el.len) == 3
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "Nim"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == "D"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxValueByIt(it.len) == "Nim"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minValueByIt(it.len) == "D"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].maxValue(el, el.len) == "Nim"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minValue(el, el.len) == "D"
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minMaxValueBy(proc (x,y: string): int = cmp(x.len, y.len)) == ("D", "Nim")
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minMaxValueByIt(it.len) == ("D", "Nim")
|
||||||
|
|
||||||
|
assert ["Go", "Nim", "C++", "D"].minMaxValue(el, el.len) == ("D", "Nim")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue