added GTK4 support

This commit is contained in:
Stefan Salewski 2019-06-22 20:24:06 +02:00
commit fd6cda7fe8
18 changed files with 320 additions and 451 deletions

16
examples/gtk3/app0.nim Normal file
View file

@ -0,0 +1,16 @@
# app0.nim -- minimal application style example
# nim c app0.nim
import gintro/[gtk, glib, gobject, gio]
proc appActivate(app: Application) =
let window = newApplicationWindow(app)
window.title = "GTK3 & Nim"
window.defaultSize = (200, 200)
showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

31
examples/gtk3/builder.nim Normal file
View file

@ -0,0 +1,31 @@
# https://developer.gnome.org/gtk3/stable/ch01s03.html
# builder.nim -- application style example using builder/glade xml file for user interface
# nim c builder.nim
import gintro/[gtk, glib, gobject, gio]
proc hello(b: Button; msg: string) =
echo "Hello", msg
proc quitApp(b: Button; app: Application) =
echo "Bye"
quit(app)
proc appActivate(app: Application) =
let builder = newBuilder()
discard builder.addFromFile("builder.ui")
let window = builder.getApplicationWindow("window")
window.setApplication(app)
var button = builder.getButton("button1")
button.connect("clicked", hello, "")
button = builder.getButton("button2")
button.connect("clicked", hello, " again...")
button = builder.getButton("quit")
button.connect("clicked", quitApp, app)
#showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

45
examples/gtk3/builder.ui Normal file
View file

@ -0,0 +1,45 @@
<interface>
<object id="window" class="GtkApplicationWindow">
<property name="visible">True</property>
<property name="title">Grid</property>
<property name="border-width">10</property>
<child>
<object id="grid" class="GtkGrid">
<property name="visible">True</property>
<child>
<object id="button1" class="GtkButton">
<property name="visible">True</property>
<property name="label">Button 1</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object id="button2" class="GtkButton">
<property name="visible">True</property>
<property name="label">Button 2</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object id="quit" class="GtkButton">
<property name="visible">True</property>
<property name="label">Quit</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
<property name="width">2</property>
</packing>
</child>
</object>
<packing>
</packing>
</child>
</object>
</interface>

22
examples/gtk3/button.nim Normal file
View file

@ -0,0 +1,22 @@
# nim c button.nim
import gintro/[gtk, glib, gobject, gio]
proc buttonClicked (button: Button) =
button.label = utf8Strreverse(button.label, -1)
proc appActivate (app: Application) =
let window = newApplicationWindow(app)
window.title = "GNOME Button"
window.defaultSize = (250, 50)
let button = newButton("Click Me")
window.add(button)
button.connect("clicked", buttonClicked)
window.showAll
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard app.run
main()

View file

@ -0,0 +1,44 @@
# https://lists.cairographics.org/archives/cairo/2016-October/027791.html
# Nim version of that plain cairo animation example
import gintro/[gtk, glib, gobject, gio, cairo]
import math
const
NumPoints = 1000
Period = 100.0
proc invalidateCb(w: Widget): bool =
queueDraw(w)
return SOURCE_CONTINUE
proc sineToPoint(x, width, height: int): float =
math.sin(x.float * math.TAU / Period) * height.float * 0.5 + height.float * 0.5
proc drawingAreaDrawCb(widget: DrawingArea; context: Context): bool =
var redrawNumber {.global.} : int
let width = getAllocatedWidth(widget)
let height = getAllocatedHeight(widget)
for i in 1 ..< NumPoints:
context.lineTo(i.float , sineToPoint(i + redrawNumber, width, height))
context.stroke
inc(redrawNumber)
return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
proc appActivate(app: Application) =
let window = newApplicationWindow(app)
window.title = "Drawing example"
window.defaultSize = (400, 400)
let drawingArea = newDrawingArea()
window.add(drawingArea)
showAll(window)
discard timeoutAdd(1000 div 60, invalidateCb, drawingArea)
connect(drawingArea, "draw", drawingAreaDrawCb)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

View file

@ -0,0 +1,53 @@
# nim c connect_args.nim
import gintro/[gtk, glib, gobject, gio]
type
O = object
i: int
proc b1Callback(button: Button; str: string) =
echo str
proc b2Callback(button: Button; o: O) =
echo "Value of field i in object o = ", o.i
proc b3Callback(button: Button; r: ref O) =
echo "Value of field i in ref to object O = ", r.i
proc b4Callback(button: Button; w: ApplicationWindow) =
if w.title == "Nim with GTK3":
w.title = "GTK3 with Nim"
else:
w.title = "Nim with GTK3"
proc appActivate (app: Application) =
var o: O
var r: ref O
new r
o.i = 1234567
r.i = 7654321
let window = newApplicationWindow(app)
let box = newBox(Orientation.vertical, 0)
window.title = "Parameters for callbacks"
let b1 = newButton("Nim with GTK3")
let b2 = newButton("Passing an object from stack")
let b3 = newButton("Passing an object from heap")
let b4 = newButton("Passing a Widget")
b1.connect("clicked", b1Callback, "is much fun.")
b2.connect("clicked", b2Callback, o)
b3.connect("clicked", b3Callback, r)
b4.connect("clicked", b4Callback, window)
box.add(b1)
box.add(b2)
box.add(b3)
box.add(b4)
window.add(box)
window.showAll
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard app.run
main()

View file

@ -0,0 +1,29 @@
# nim c count_button.nim
import gintro/[gtk, glib, gobject, gio]
type
CountButton = ref object of Button
counter: int
proc buttonClicked (button: CountButton; decrement: int) =
dec(button.counter, decrement)
button.label = "Counter: " & $button.counter
echo "Counter is now: ", button.counter
proc appActivate (app: Application) =
var button: CountButton
let window = newApplicationWindow(app)
window.title = "Count Button"
initButton(button, "Counting down from 100 by 5")
button.counter = 100
window.add(button)
button.connect("clicked", buttonClicked, 5)
window.showAll
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard app.run
main()

View file

@ -0,0 +1,25 @@
import gintro/cairo
import drawingarea
from math import PI
proc extents(): (float, float, float, float) =
(0.0, 0.0, 100.0, 100.0) # ugly float literals
# draw to cairo context
proc draw(cr: cairo.Context) =
cr.setSource(1, 1, 1) # set background color and paint
cr.paint
cr.setSource(0, 0, 0) # forground color
cr.arc(20, 30, 10, 0, 5) # nearly a circle
cr.newSubPath # do not join the two arcs
cr.arc(70, 60, 20, 0, math.PI)
cr.stroke # finally do it
proc main =
var data: PDA_Data
data.draw = draw
data.extents = extents
data.windowSize = (800, 600)
newDisplay(data)
main()

View file

@ -0,0 +1,317 @@
# Plain demo for zooming, panning, scrolling with GTK DrawingArea
# (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
# Nim version April 2019
# License MIT
# This version of the demo program uses a separate proc paint()
# which allocates a custom surface for buffered drawing.
# That may be not really necessary, for simple drawings doing all
# the drawing in the "draw" call back is easier and faster. But for
# more complicated drawing operations, for example when using a
# background grid, which is a bit larger than the window size and
# is reused when scrolling, a custom surface may be useful.
# And finally that custom surface and custom cairo context is an
# important test for the language bindings.
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
const
ZoomFactorMouseWheel = 1.1
ZoomFactorSelectMax = 10 # ignore zooming in tiny selection
ZoomNearMousepointer = true # mouse wheel zooming -- to mouse-pointer or center
SelectRectCol = [0.0, 0, 1, 0.5] # blue with transparency
discard """
Zooming, scrolling, panning...
|-------------------------|
|<-------- A ------------>|
| |
| |---------------| |
| | <---- a ----->| |
| | visible | |
| |---------------| |
| |
| |
|-------------------------|
a is the visible, zoomed in area == darea.allocatedWidth
A is the total data range
A/a == userZoom >= 1
For horizontal adjustment we use
hadjustment.setUpper(darea.allocatedWidth * userZoom) == A
hadjustment.setPageSize(darea.allocatedWidth) == a
So hadjustment.value == left side of visible area
Initially, we set userZoom = 1, scale our data to fit into darea.allocatedWidth
and translate the origin of our data to (0, 0)
Zooming: Mouse wheel or selecting a rectangle with left mouse button pressed
Scrolling: Scrollbars
Panning: Moving mouse while middle mouse button pressed
"""
# drawing area and scroll bars in 2x2 table (PDA == Plain Drawing Area)
type
PosAdj = ref object of Adjustment
handlerID: uint64
proc newPosAdj: PosAdj =
initAdjustment(result, 0, 0, 1, 1, 10, 1)
type
PDA_Data* = object
draw*: proc (cr: Context)
extents*: proc (): tuple[x, y, w, h: float]
windowSize*: tuple[w, h: int]
type
PDA = ref object of Grid
zoomNearMousepointer: bool
selecting: bool
userZoom: float
surf: Surface
darea: DrawingArea
hadjustment: PosAdj
vadjustment: PosAdj
hscrollbar: Scrollbar
vscrollbar: Scrollbar
fullScale: float
dataX: float
dataY: float
dataWidth: float
dataHeight: float
lastButtonDownPosX: float
lastButtonDownPosY: float
lastMousePosX: float
lastMousePosY: float
zoomRectX1: float
zoomRectY1: float
drawWorld: proc (cr: Context)
extents: proc (): tuple[x, y, w, h: float]
proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
if this.surf.isNil: return
cr.setSource(patternCreateForSurface(this.surf))
cr.paint
if this.selecting:
cr.rectangle(this.lastButtonDownPosX, this.lastButtonDownPosY,
this.zoomRectX1 - this.lastButtonDownPosX,
this.zoomRectY1 - this.lastButtonDownPosY)
cr.setSource(0, 0, 1, 0.5) # SELECT_RECT_COL) # 0, 0, 1, 0.5
cr.fillPreserve
cr.setSource(0, 0, 0)
cr.setLineWidth(2)
cr.stroke
return SignalEventStopPropagation
#return true # TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further.
# clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent()
proc updateVal(adj: PosAdj; d: float) =
adj.signalHandlerBlock(adj.handlerID)
adj.setValue(max(0.0, min(adj.value + d, adj.upper - adj.pageSize)))
adj.signalHandlerUnblock(adj.handlerID)
proc updateAdjustments(this: PDA; dx, dy: float) =
this.hadjustment.setUpper(this.darea.allocatedWidth.float * this.userZoom)
this.vadjustment.setUpper(this.darea.allocatedHeight.float * this.userZoom)
this.hadjustment.setPageSize(this.darea.allocatedWidth.float)
this.vadjustment.setPageSize(this.darea.allocatedHeight.float)
updateVal(this.hadjustment, dx)
updateVal(this.vadjustment, dy)
# maybe for optimization we should only allocate a new surface when its size has changed.
proc paint(this: PDA) =
if this.surf != nil:
destroy(this.surf) # manually destroy surface -- GC would do it for us, but GC is slow...
this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight)
let cr = newContext(this.surf)
cr.translate(this.hadjustment.upper * 0.5 - this.hadjustment.value, # our origin is the center
this.vadjustment.upper * 0.5 - this.vadjustment.value)
cr.scale(this.fullScale * this.userZoom, this.fullScale * this.userZoom)
cr.translate(-this.dataX - this.dataWidth * 0.5, -this.dataY - this.dataHeight * 0.5)
this.drawWorld(cr) # call the user provided drawing function
destroy(cr) # we can also manually destroy the context here, but GC would do it for us
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure; this: PDA): bool =
#this.updateAdjustments(0, 0)
(this.dataX, this.dataY, this.dataWidth,
this.dataHeight) = this.extents() # query user defined size
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
this.darea.allocatedHeight.float / this.dataHeight)
this.paint
proc hscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
pda.hadjustment.setUpper(r.width.float * pda.userZoom)
pda.hadjustment.setPageSize(r.width.float)
proc vscrollbarSizeAllocateCallback(s: Scrollbar; r: gdk.Rectangle; pda: PDA) =
pda.vadjustment.setUpper(r.height.float * pda.userZoom)
pda.vadjustment.setPageSize(r.height.float)
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
this.updateAdjustments(dx, dy)
this.paint
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
# event coordinates to user space
proc getUserCoordinates(this: PDA; eventX, eventY: float): (float, float) =
((eventX - this.hadjustment.upper * 0.5 + this.hadjustment.value) / (
this.fullScale * this.userZoom) + this.dataX + this.dataWidth * 0.5,
(eventY - this.vadjustment.upper * 0.5 + this.vadjustment.value) / (
this.fullScale * this.userZoom) + this.dataY + this.dataHeight * 0.5)
proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
let state = getState(event)
let (x, y) = event.getCoords
if (state.contains(button1)): # selecting
this.selecting = true
this.zoomRectX1 = x
this.zoomRectY1 = y
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.allocatedHeight)
elif button2 in state: # panning
this.updateAdjustmentsAndPaint(this.lastMousePosX - x, this.lastMousePosY - y)
this.lastMousePosX = x
this.lastMousePosY = y
#event.request # request more motion events ?
# zooming with mouse wheel -- data near mouse pointer should not move if possible!
# hadjustment.value + event.x is the position in our zoomed_in world, (userZoom / z0 - 1)
# is the relative movement caused by zooming
proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
let z0 = this.userZoom
var d = getScrollDirection(event)
var (x, y) = event.getCoords
if d == ScrollDirection.up:
this.userZoom *= ZoomFactorMouseWheel
elif d == ScrollDirection.down:
this.userZoom /= ZoomFactorMouseWheel
if (this.userZoom < 1):
this.userZoom = 1
if this.zoomNearMousepointer:
this.updateAdjustmentsAndPaint((this.hadjustment.value + x) * (this.userZoom / z0 - 1),
(this.vadjustment.value + y) * (this.userZoom / z0 - 1))
else: # zoom to center
this.updateAdjustmentsAndPaint((this.hadjustment.value +
this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
(this.vadjustment.value + this.darea.allocatedHeight.float * 0.5) * (this.userZoom / z0 - 1))
proc buttonPressEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
this.lastMousePosX = x
this.lastMousePosY = y
this.lastButtonDownPosX = x
this.lastButtonDownPosY = y
echo "buttonPressEvent", x, " ", y
(x, y) = this.getUserCoordinates(x, y)
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
# zoom into selected rectangle and center it
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; this: PDA): bool =
var (x, y) = event.getCoords
var b = getButton(event)
if b == 1:
this.selecting = false
let z1 = min(this.darea.allocatedWidth.float / (
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
this.lastButtonDownPosY - y).abs)
if z1 < ZoomFactorSelectMax: # else selection rectangle will persist, we may output a message...
this.userZoom *= z1
this.updateAdjustmentsAndPaint(
((2 * this.hadjustment.value + x + this.lastButtonDownPosX) * z1 -
this.darea.allocatedWidth.float) * 0.5 - this.hadjustment.value,
((2 * this.vadjustment.value + y + this.lastButtonDownPosY) * z1 -
this.darea.allocatedHeight.float) * 0.5 - this.vadjustment.value)
proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
pda.paint
pda.darea.queueDrawArea(0, 0, pda.darea.allocatedWidth, pda.darea.allocatedHeight)
proc newPDA: PDA =
initGrid(result)
result.zoomNearMousepointer = ZoomNearMousepointer # mouse wheel zooming
result.userZoom = 1.0
result.darea = newDrawingArea()
result.darea.setHExpand
result.darea.setVExpand
result.darea.connect("draw", drawingAreaDrawCb, result)
result.darea.connect("configure-event", dareaConfigureCallback, result)
result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
EventFlag.scroll, button1Motion, button2Motion, pointerMotionHint})
result.darea.connect("motion-notify-event", onMotion, result)
result.darea.connect("scroll_event", scrollEvent, result)
result.darea.connect("button_press_event", buttonPressEvent, result)
result.darea.connect("button_release_event", buttonReleaseEvent, result)
result.hadjustment = newPosAdj()
result.hadjustment.handlerID = result.hadjustment.connect("value-changed",
onAdjustmentEvent, result)
result.vadjustment = newPosAdj()
result.vadjustment.handlerID = result.vadjustment.connect("value-changed",
onAdjustmentEvent, result)
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
result.hscrollbar.setHExpand
result.vscrollbar.setVExpand
result.hscrollbar.connect("size-allocate", hscrollbarSizeAllocateCallback, result)
result.vscrollbar.connect("size-allocate", vscrollbarSizeAllocateCallback, result)
result.attach(result.darea, 0, 0, 1, 1)
result.attach(result.vscrollbar, 1, 0, 1, 1)
result.attach(result.hscrollbar, 0, 1, 1, 1)
proc appActivate(app: Application; initData: ref PDA_Data) =
let window = newApplicationWindow(app)
window.title = "Drawing example"
window.defaultSize = (initData.windowSize[0], initData.windowSize[1])
let pda = newPDA()
pda.drawWorld = initData.draw
pda.extents = initData.extents
window.add(pda)
showAll(window)
proc newDisplay*(initData: PDA_Data) =
let app = newApplication("org.gtk.example")
var d = new (ref PDA_Data)
d[] = initData
connect(app, "activate", appActivate, d)
discard run(app)
when isMainModule:
const # arbitrary locations for our data
DataX = 150.0
DataY = 250.0
DataWidth = 200.0
DataHeight = 120.0
# we need two user defined functions -- one gives the extent of the graphics,
# and the other does the cairo drawing using a cairo context.
# bounding box of user data -- x, y, w, h -- top left corner, width, height
proc worldExtents(): (float, float, float, float) =
(DataX, DataY, DataWidth, DataHeight) # current extents of our user world
# draw to cairo context
proc drawWorld(cr: cairo.Context) =
cr.setSource(1, 1, 1)
cr.paint
cr.setSource(0, 0, 0)
cr.setLineWidth(2)
var i = 0.0
while true:
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
break
cr.rectangle(DataX + i, DataY + i, DataWidth - 2 * i, DataHeight - 2 * i)
i += 10.0
cr.stroke
proc test =
var data: PDA_Data
data.draw = drawWorld
data.extents = worldExtents
data.windowSize = (800, 600)
newDisplay(data)
test()

27
examples/gtk3/gaction.nim Normal file
View file

@ -0,0 +1,27 @@
# https://wiki.gnome.org/HowDoI/GAction
# nim c gaction.nim
import gintro/[gtk, glib, gobject, gio]
proc saveCb(action: SimpleAction; v: Variant) =
echo "saveCb"
proc appActivate(app: Application) =
let window = newApplicationWindow(app)
let action = newSimpleAction("save")
discard action.connect("activate", saveCB)
window.actionMap.addAction(action)
let button = newButton()
button.label = "Save"
window.add(button)
button.setActionName("win.save")
var h: cstringArray = allocCstringArray(["<Control><Shift>S"])
setAccelsForAction(app, "win.save", h)
deallocCStringArray(h)
showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

22
examples/gtk3/label.nim Normal file
View file

@ -0,0 +1,22 @@
# nim c label.nim
import gintro/[gtk, glib, gobject, gio]
proc appActivate(app: Application) =
let window = newApplicationWindow(app)
let label = newLabel("Yellow text on green background")
let cssProvider = newCssProvider()
let data = "label {color: yellow; background: green;}"
#discard cssProvider.loadFromPath("doesnotexist")
discard cssProvider.loadFromData(data)
let styleContext = label.getStyleContext
assert styleContext != nil
addProvider(styleContext, cssProvider, STYLE_PROVIDER_PRIORITY_USER)
window.add(label)
showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

102
examples/gtk3/listview.nim Normal file
View file

@ -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", appendItem, entry)
connect(remove, "clicked", removeItem, selection)
connect(removeAll, "clicked", onRemoveAll, selection)
showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

105
examples/gtk3/menubar.nim Normal file
View file

@ -0,0 +1,105 @@
# https://developer.gnome.org/glib/stable/glib-GVariant.html
# https://developer.gnome.org/glib/stable/glib-GVariantType.html
# https://wiki.gnome.org/HowDoI/GMenu
# https://wiki.gnome.org/HowDoI/GAction
# nim c menubar.nim
import gintro/[gtk, glib, gobject, gio]
from strutils import `%`, format
# https://github.com/GNOME/glib/blob/master/gio/tests/gapplication-example-actions.c
proc activateToggleAction(action: SimpleAction; parameter: Variant; app: Application) =
app.hold # hold/release taken over from C example, there may be reasons...
block:
echo format("action $1 activated", action.name)
let state: Variant = action.state
let b = state.getBoolean
action.state = newVariantBoolean(not b)
echo format("state change $1 -> $2", b, not b)
app.release
proc activateStatefulAction(action: SimpleAction; parameter: Variant; app: Application) =
app.hold
block:
echo format("action $1 activated", action.name)
let state: Variant = action.state
var l: uint64
let oldState = state.getString(l) # yes uint64 parameter is a bit ugly
let newState = parameter.getString(l)
action.state = newVariantString(newState)
echo format("state change $1 -> $2", oldState, newState)
app.release
proc quitProgram(action: SimpleAction; parameter: Variant; app: Application) =
quit(app)
proc appStartup(app: Application) =
let quit = newSimpleAction("quit") # here we create the actions for whole app
connect(quit, "activate", quitProgram, app)
app.addAction(quit)
let menu = gio.newMenu() # root of all menus
block: # plain stateless menu
let subMenu = gio.newMenu()
menu.appendSubMenu("Application", submenu)
# let section = gio.newMenu() # no separating section needed here
# submenu.appendSection(nil, section)
# section.append("Quit", "app.quit")
submenu.append("Quit", "app.quit")
block: #stateful menu with radio items
let subMenu = gio.newMenu()
menu.appendSubMenu("Layout", submenu)
let subMenu2 = gio.newMenu()
submenu.appendSubMenu("justify", submenu2)
let section = gio.newMenu()
submenu2.appendSection(nil, section)
section.append("left", "win.justify::left")
section.append("center", "win.justify::center")
section.append("right", "win.justify::right")
block: # and finally a toggle menu
let subMenu = gio.newMenu()
menu.appendSubMenu("Spelling", submenu)
let section = gio.newMenu()
submenu.appendSection(nil, section)
section.append("Check", "win.toggleSpellCheck")
# finally add the menubar
setMenuBar(app, menu)
proc appActivate(app: Application) =
let window = newApplicationWindow(app)
window.title = "GTK3 App with Menubar"
window.defaultSize = (500, 200)
window.position = WindowPosition.center
block: # creat the window related actions
let v = newVariantBoolean(true)
let spellCheck = newSimpleActionStateful("toggleSpellCheck", nil, v)
connect(spellCheck, "activate", activateToggleAction, app)
window.actionMap.addAction(spellCheck)
block:
let v = newVariantString("left") # default value and
let vt = newVariantType("s") # string (value type)
let justifyAction = newSimpleActionStateful("justify", vt, v)
connect(justifyAction, "activate", activateStatefulAction, app)
window.actionMap.addAction(justifyAction)
let button = newButton()
button.label = "Justify Center"
#window.add(button) # do not add it here already: (menubar:10010): Gtk-WARNING **: 22:00:33.230: actionhelper:
# action win.justify can't be activated due to parameter type mismatch (parameter type s, target type NULL)
button.setDetailedActionName("win.justify::center")
#button.setActionName("app.quit") # for a stateless action
var h: cstringArray = allocCstringArray(["<Control><Shift>R"]) # ugly
setAccelsForAction(app, "win.justify::right", h)
deallocCStringArray(h)
window.add(button)
showAll(window)
proc main =
let app = newApplication("app.example")
connect(app, "startup", appStartup)
connect(app, "activate", appActivate)
echo "GTK Version $1.$2.$3" % [$majorVersion(), $minorVersion(), $microVersion()]
let status = run(app)
quit(status)
main()

16
examples/gtk3/t0.nim Normal file
View file

@ -0,0 +1,16 @@
# nim c t0.nim
import gintro/[gtk, gobject]
proc bye(w: Window) =
mainQuit()
echo "Bye..."
proc main =
gtk.init()
let window = newWindow()
window.title = "First Test"
window.connect("destroy", bye)
window.showAll
gtk.main()
main()