various fixes

This commit is contained in:
Stefan Salewski 2019-04-14 12:56:38 +02:00
commit 8a59ea33d0
5 changed files with 668 additions and 50 deletions

View file

@ -841,14 +841,13 @@ main()
== A more advanced example for cairo drawing with zooming, panning, scrolling == 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). 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).
With the latest fix to Nim GTK3 bingings it seems to work fine, but some rewriting for optimized performance With the latest fix to Nim GTK3 bindings it seems to work fine, but some rewriting for optimized performance
may be needed, I have to investigate that. may be needed, I have to investigate that. Cairo surface is currently manually freed, because GC may have a too large delay.
Later we may use Destructors for that.
NOTE: The latest needed gintro patch is not yet available, I will provide it soon... [[drawingarea.nim]]
[[cairo2.nim]]
[source,nim] [source,nim]
.cairo2.nim .drawingarea.nim
---- ----
# Plain demo for zooming, panning, scrolling with GTK DrawingArea # Plain demo for zooming, panning, scrolling with GTK DrawingArea
# (c) S. Salewski, 21-DEC-2010 (initial Ruby version) # (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
@ -879,7 +878,7 @@ proc drawWorld(cr: cairo.Context) =
cr.rectangle(DataX, DataY, DataWidth, DataHeight) cr.rectangle(DataX, DataY, DataWidth, DataHeight)
var i = 10.0 var i = 10.0
while true: while true:
if [DataWidth - 2.0 * i, DataHeight - 2.0 * i].min <= 0: if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
break break
cr.rectangle(DataX + i, DataY + i, DataWidth - 2.0 * i, cr.rectangle(DataX + i, DataY + i, DataWidth - 2.0 * i,
DataHeight - 2.0 * i) DataHeight - 2.0 * i)
@ -933,7 +932,7 @@ proc newPosAdj: PosAdj =
initAdjustment(result, 0, 0, 1, 1, 10, 1) initAdjustment(result, 0, 0, 1, 1, 10, 1)
type type
PDA = ref object of Table PDA = ref object of Grid
zoomNearMousepointer: bool zoomNearMousepointer: bool
selecting: bool selecting: bool
userZoom: float userZoom: float
@ -973,7 +972,7 @@ proc drawingAreaDrawCb(darea: DrawingArea; cr: Context; this: PDA): bool =
# clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent() # clamp to correct values, 0 <= value <= (adj.upper - adj.pageSize), block calling onAdjustmentEvent()
proc updateVal(adj: PosAdj; d: float) = proc updateVal(adj: PosAdj; d: float) =
adj.signalHandlerBlock(adj.handlerID) adj.signalHandlerBlock(adj.handlerID)
adj.setValue([0.0, [adj.value + d, adj.upper - adj.pageSize].min].max) adj.setValue(max(0.0, min(adj.value + d, adj.upper - adj.pageSize)))
adj.signalHandlerUnblock(adj.handlerID) adj.signalHandlerUnblock(adj.handlerID)
proc updateAdjustments(this: PDA; dx, dy: float) = proc updateAdjustments(this: PDA; dx, dy: float) =
@ -991,7 +990,7 @@ proc updateAdjustments(this: PDA; dx, dy: float) =
# or maybe even better do all drwing from within "draw" callback? # or maybe even better do all drwing from within "draw" callback?
proc paint(this: PDA) = proc paint(this: PDA) =
if this.surf != nil: if this.surf != nil:
cairosurfaceDestroy(this.surf.impl) # we may have to fix that later for automatically destroy cairosurfaceDestroy(this.surf.impl) # we may have to fix that later for automatically destroy
this.surf = this.darea.window.createSimilarSurface(Content.color, this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight) this.darea.allocatedWidth, this.darea.allocatedHeight)
let cr = newContext(this.surf) let cr = newContext(this.surf)
@ -1007,8 +1006,8 @@ proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure;
this.updateAdjustments(0, 0) this.updateAdjustments(0, 0)
(this.dataX, this.dataY, this.dataWidth, (this.dataX, this.dataY, this.dataWidth,
this.dataHeight) = getWorldExtends() # query user defined size this.dataHeight) = getWorldExtends() # query user defined size
this.fullScale = [this.darea.allocatedWidth.float / this.dataWidth, this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
this.darea.allocatedHeight.float / this.dataHeight].min this.darea.allocatedHeight.float / this.dataHeight)
this.paint this.paint
proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) = proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
@ -1017,11 +1016,17 @@ proc updateAdjustmentsAndPaint(this: PDA; dx, dy: float) =
this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth, this.darea.queueDrawArea(0, 0, this.darea.allocatedWidth,
this.darea.allocatedHeight) 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 = proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
var state: ModifierType let state = getState(event)
discard event.state(state) let (x, y) = event.getCoords
var x, y: float #(x, y) = this.getUserCoordinates(x, y)
discard event.getCoords(x, y)
if (state.contains(button1)): # selecting if (state.contains(button1)): # selecting
this.selecting = true this.selecting = true
this.zoomRectX1 = x this.zoomRectX1 = x
@ -1040,10 +1045,8 @@ proc onMotion(darea: DrawingArea; event: EventMotion; this: PDA): bool =
# is the relative movement caused by zooming # is the relative movement caused by zooming
proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool = proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
let z0 = this.userZoom let z0 = this.userZoom
var d: ScrollDirection var d = getScrollDirection(event)
discard scrollDirection(event, d) var (x, y) = event.getCoords
var x, y: float
discard event.getCoords(x, y)
if d == ScrollDirection.up: if d == ScrollDirection.up:
this.userZoom *= ZOOM_FACTOR_MOUSE_WHEEL this.userZoom *= ZOOM_FACTOR_MOUSE_WHEEL
elif d == ScrollDirection.down: elif d == ScrollDirection.down:
@ -1059,38 +1062,30 @@ proc scrollEvent(darea: DrawingArea; event: EventScroll; this: PDA): bool =
this.updateAdjustmentsAndPaint((this.hadjustment.value + this.updateAdjustmentsAndPaint((this.hadjustment.value +
this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1), this.darea.allocatedWidth.float * 0.5) * (this.userZoom / z0 - 1),
(this.vadjustment.value + (this.vadjustment.value +
this.darea.allocatedHeight.float * 0.5) * (this.userZoom / z0 - 1)) this.darea.allocatedHeight.float * 0.5) * (
this.userZoom / z0 - 1))
# 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 buttonPressEvent(darea: DrawingArea; event: EventButton; proc buttonPressEvent(darea: DrawingArea; event: EventButton;
this: PDA): bool = this: PDA): bool =
var x, y: float var (x, y) = event.getCoords
discard event.getCoords(x, y)
(x, y) = this.getUserCoordinates(x, y)
echo "User coordinates: ", x, ' ', y, "\n" # to verify getUserCoordinates()
this.lastMousePosX = x this.lastMousePosX = x
this.lastMousePosY = y this.lastMousePosY = y
this.lastButtonDownPosX = x this.lastButtonDownPosX = x
this.lastButtonDownPosY = y 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 # zoom into selected rectangle and center it
proc buttonReleaseEvent(darea: DrawingArea; event: EventButton; proc buttonReleaseEvent(darea: DrawingArea; event: EventButton;
this: PDA): bool = this: PDA): bool =
var x, y: float var (x, y) = event.getCoords
discard event.getCoords(x, y) var b = getButton(event)
var b: int
discard button(event, b)
if b == 1: if b == 1:
this.selecting = false this.selecting = false
let z1 = [this.darea.allocatedWidth.float / ( let z1 = min(this.darea.allocatedWidth.float / (
this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / ( this.lastButtonDownPosX - x).abs, this.darea.allocatedHeight.float / (
this.lastButtonDownPosY - y).abs].min this.lastButtonDownPosY - y).abs)
if z1 < ZOOM_FACTOR_SELECT_MAX: # else selection rectangle will persist, we may output a message... if z1 < ZOOM_FACTOR_SELECT_MAX: # else selection rectangle will persist, we may output a message...
this.userZoom *= z1 this.userZoom *= z1
this.updateAdjustmentsAndPaint( this.updateAdjustmentsAndPaint(
@ -1105,13 +1100,14 @@ proc onAdjustmentEvent(this: PosAdj; pda: PDA) =
pda.darea.allocatedHeight) pda.darea.allocatedHeight)
proc newPDA: PDA = proc newPDA: PDA =
initTable(result, 2, 2, false) initGrid(result)
result.zoomNearMousepointer = ZOOM_NEAR_MOUSEPOINTER # mouse wheel zooming result.zoomNearMousepointer = ZOOM_NEAR_MOUSEPOINTER # mouse wheel zooming
result.selecting = false result.selecting = false
result.userZoom = 1.0 result.userZoom = 1.0
result.surf = nil result.surf = nil
result.darea = newDrawingArea() result.darea = newDrawingArea()
result.darea.setHExpand#(true)
result.darea.setVExpand#(true)
result.darea.connect("draw", drawingAreaDrawCb, result) result.darea.connect("draw", drawingAreaDrawCb, result)
result.darea.connect("configure-event", dareaConfigureCallback, result) result.darea.connect("configure-event", dareaConfigureCallback, result)
result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease, result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
@ -1130,12 +1126,11 @@ proc newPDA: PDA =
onAdjustmentEvent, result) onAdjustmentEvent, result)
result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment) result.hscrollbar = newScrollbar(Orientation.horizontal, result.hadjustment)
result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment) result.vscrollbar = newScrollbar(Orientation.vertical, result.vadjustment)
result.attach(result.darea, 0, 1, 0, 1, {AttachFlag.expand, AttachFlag.fill}, { result.hscrollbar.setHExpand#(true)
AttachFlag.expand, AttachFlag.fill}, 0, 0) result.vscrollbar.setVExpand#(true)
result.attach(result.hscrollbar, 0, 1, 1, 2, {AttachFlag.expand, result.attach(result.darea, 0, 0, 1, 1)
AttachFlag.fill}, {}, 0, 0) result.attach(result.vscrollbar, 1, 0, 1, 1)
result.attach(result.vscrollbar, 1, 2, 0, 1, {}, {AttachFlag.expand, result.attach(result.hscrollbar, 0, 1, 1, 1)
AttachFlag.fill}, 0, 0)
proc appActivate(app: Application) = proc appActivate(app: Application) =
let window = newApplicationWindow(app) let window = newApplicationWindow(app)

297
examples/drawingarea.nim Normal file
View file

@ -0,0 +1,297 @@
# Plain demo for zooming, panning, scrolling with GTK DrawingArea
# (c) S. Salewski, 21-DEC-2010 (initial Ruby version)
# Nim version April 2019 -- well first draft :-)
# License MIT
import gintro/[gtk, gdk, glib, gobject, gio, cairo]
const
DataX = 150.0
DataY = 250.0
DataWidth = 200.0
DataHeight = 120.0
# The following two functions are intended to be user defined -- we have still
# to investigate how to do that best.
# bounding box of user data -- x, y, w, h -- top left corner, width, height
proc getWorldExtends(): (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)
cr.rectangle(DataX, DataY, DataWidth, DataHeight)
var i = 10.0
while true:
if min(DataWidth - 2.0 * i, DataHeight - 2.0 * i) <= 0:
break
cr.rectangle(DataX + i, DataY + i, DataWidth - 2.0 * i,
DataHeight - 2.0 * i)
i += 10.0
cr.stroke
const
ZOOM_FACTOR_MOUSE_WHEEL = 1.1
ZOOM_FACTOR_SELECT_MAX = 10 # ignore zooming in tiny selection
ZOOM_NEAR_MOUSEPOINTER = true # mouse wheel zooming -- to mousepointer or center
SELECT_RECT_COL = [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 = 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
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 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 we should fix this for latest GTK3, see
# https://developer.gnome.org/gdk3/stable/gdk3-Cairo-Interaction.html
# so use gdk_drawing_context_get_cairo_context ()
# or maybe even better do all drwing from within "draw" callback?
proc paint(this: PDA) =
if this.surf != nil:
cairosurfaceDestroy(this.surf.impl) # we may have to fix that later for automatically destroy
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)
drawWorld(cr) # call the user provided drawing fonction
proc dareaConfigureCallback(darea: DrawingArea; event: EventConfigure;
this: PDA): bool =
this.updateAdjustments(0, 0)
(this.dataX, this.dataY, this.dataWidth,
this.dataHeight) = getWorldExtends() # query user defined size
this.fullScale = min(this.darea.allocatedWidth.float / this.dataWidth,
this.darea.allocatedHeight.float / this.dataHeight)
this.paint
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
#(x, y) = this.getUserCoordinates(x, y)
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 *= ZOOM_FACTOR_MOUSE_WHEEL
elif d == ScrollDirection.down:
this.userZoom /= ZOOM_FACTOR_MOUSE_WHEEL
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 < ZOOM_FACTOR_SELECT_MAX: # 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 = ZOOM_NEAR_MOUSEPOINTER # mouse wheel zooming
result.selecting = false
result.userZoom = 1.0
result.surf = nil
result.darea = newDrawingArea()
result.darea.setHExpand#(true)
result.darea.setVExpand#(true)
result.darea.connect("draw", drawingAreaDrawCb, result)
result.darea.connect("configure-event", dareaConfigureCallback, result)
result.darea.addEvents({EventFlag.buttonPress, EventFlag.buttonRelease,
EventFlag.scroll,
EventFlag.button1Motion, EventFlag.button2Motion,
EventFlag.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#(true)
result.vscrollbar.setVExpand#(true)
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) =
let window = newApplicationWindow(app)
window.title = "Drawing example"
window.defaultSize = (400, 400)
let pda = newPDA()
window.add(pda)
showAll(window)
proc main =
let app = newApplication("org.gtk.example")
connect(app, "activate", appActivate)
discard run(app)
main()

View file

@ -1,6 +1,6 @@
# Package # Package
version = "0.4.11" version = "0.4.12"
author = "Stefan Salewski" author = "Stefan Salewski"
description = "High level GObject-Introspection based GTK3 bindings" description = "High level GObject-Introspection based GTK3 bindings"
license = "MIT" license = "MIT"

View file

@ -145,7 +145,7 @@ proc $1$2 {.cdecl.} =
r1s.add(resu & "\n") r1s.add(resu & "\n")
all = all.replace(")", "; data: pointer)") all = all.replace(")", "; data: pointer)")
r1s = r1s % [$procNameCdecl, all, $p, wts, ats] r1s = r1s % [$procNameCdecl, all, $p, wts, ats]
echo r1s #echo r1s
result = parseStmt(r1s) result = parseStmt(r1s)
if not ignoreArg.boolVal: if not ignoreArg.boolVal:
ahl = ahl.replace(")", "; arg: " & ats & ")") ahl = ahl.replace(")", "; arg: " & ats & ")")
@ -175,7 +175,7 @@ proc $1(self: $2; p: proc $3; a: $4): culong {.discardable.} =
sc$5(self, $6, cast[pointer](ar[])) sc$5(self, $6, cast[pointer](ar[]))
$1($7, $8, $9) $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)]
echo r2s #echo r2s
result.add(parseStmt(r2s)) result.add(parseStmt(r2s))
template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped = template connect*(widget: gobject.Object; signal: string; p: typed; arg: typed): untyped =

View file

@ -1,5 +1,5 @@
# High level gobject-introspection based GTK3 bindings for the Nim programming language # High level gobject-introspection based GTK3 bindings for the Nim programming language
# v 0.4.12 2019-APR-11 # v 0.4.12 2019-APR-14
# (c) S. Salewski 2018 # (c) S. Salewski 2018
# https://wiki.gnome.org/Projects/GObjectIntrospection # https://wiki.gnome.org/Projects/GObjectIntrospection
@ -460,6 +460,12 @@ proc genP(info: GICallableInfo; genProxy = false; binfo: GIBaseInfo = nil): (str
if genProxy and (str == "string" or str == "cstring") and mayBeNil: # v0.4.11 if genProxy and (str == "string" or str == "cstring") and mayBeNil: # v0.4.11
#str.add(" = nil") #str.add(" = nil")
str.add(" = \"\"") str.add(" = \"\"")
if genProxy and m == 0:
if sym.contains("_set_") and str == "bool":
#echo "TTTTTTTTTT", sym
str.add(" = true")
if genProxy and isProxyCandidate(t) and mayBeNil: if genProxy and isProxyCandidate(t) and mayBeNil:
#if not str.contains("|") and (gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT): #if not str.contains("|") and (gArgInfoGetDirection(arg) == GIDirection.OUT or gArgInfoGetDirection(arg) == GIDirection.INOUT):
# str.add(" = cast[" & str & "](nil)") # str.add(" = cast[" & str & "](nil)")
@ -573,6 +579,15 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) =
var ret2 = gCallableInfoGetReturnType(minfo) var ret2 = gCallableInfoGetReturnType(minfo)
#var ret22 = gCallableInfoGetReturnType(minfo) #var ret22 = gCallableInfoGetReturnType(minfo)
for run in 0 .. 1: for run in 0 .. 1:
#if run == 0:
# if asym.startsWith("set"):
# if gCallableInfoGetNArgs(minfo) == 1 or (gCallableInfoIsMethod(minfo) and gCallableInfoGetNArgs(minfo) == 2):
# echo "XXXXXXXXXX", asym, " ", gCallableInfoGetNArgs(minfo)
if run == 1: if run == 1:
#if not (asym != "errorQuark" and asym != "getPlugin" and asym != "quark"): continue #if not (asym != "errorQuark" and asym != "getPlugin" and asym != "quark"): continue
@ -589,6 +604,10 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) =
asym = asym[3 .. ^1] asym = asym[3 .. ^1]
asym[0] = asym[0].toLowerAscii asym[0] = asym[0].toLowerAscii
if asym.startsWith("set"): if asym.startsWith("set"):
#if plist.contains(" = true"):
#echo "ZZZZZZ ", plist, " ", arglist
plist = plist.replace(": bool = true", ": bool")
asym = asym[3 .. ^1] asym = asym[3 .. ^1]
asym[0] = asym[0].toLowerAscii asym[0] = asym[0].toLowerAscii
asym = '`' & asym & '=' & '`' asym = '`' & asym & '=' & '`'
@ -601,12 +620,18 @@ proc writeMethod(info: GIBaseInfo; minfo: GIFunctionInfo; genProxy = false) =
if gBaseInfoGetType(iface) == GIInfoType.Object and gBaseInfoGetName(iface) != "ParamSpec": if gBaseInfoGetType(iface) == GIInfoType.Object and gBaseInfoGetName(iface) != "ParamSpec":
isGObject = true isGObject = true
(plist, arglist, replist) = genP(mInfo, true, info) (plist, arglist, replist) = genP(mInfo, true, info)
if asym[^2] == '=':
plist = plist.replace(": bool = true", ": bool")
if (gFunctionInfoGetFlags(mInfo).int and GIFunctionInfoFlags.IS_CONSTRUCTOR.int) != 0: if (gFunctionInfoGetFlags(mInfo).int and GIFunctionInfoFlags.IS_CONSTRUCTOR.int) != 0:
if info != nil: if info != nil:
asym = asym.replace("new", "new" & manglename(gBaseInfoGetName(info))) asym = asym.replace("new", "new" & manglename(gBaseInfoGetName(info)))
if fixedProcNames.contains(sym): if fixedProcNames.contains(sym):
asym = fixedProcNames[sym] asym = fixedProcNames[sym]
for i in 0 .. 1: for i in 0 .. 1:
#if i > 0 and isGObject:
# echo "XYZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ", asym
var hhh = "\nproc " & asym & EM & plist var hhh = "\nproc " & asym & EM & plist
if i > 0: if i > 0:
let semi = if hhh.contains("()"): "" else: "; " let semi = if hhh.contains("()"): "" else: "; "
@ -1401,6 +1426,237 @@ proc processInfo(i: GIBaseInfo) =
writeConst(i) writeConst(i)
else: assert(false) else: assert(false)
# Gtk objects builder may provide. Only for Gtk module, so Gtk prefix is assume.
# Other objects loke gtkSourceView are separated!
# This a guessed list, so it may be inaccurate.
const GtkBuilderObjects = """
AboutDialog
AccelLabel
ActionBar
ActionGroup
Alignment
AppChooserButton
AppChooserDialog
AppChooserWidget
Application
ApplicationWindow
Arrow
AspectFrame
Assistant
Box
Builder
Button
ButtonBox
Calendar
CellAreaBox
CellView
CheckButton
CheckMenuItem
ColorButton
ColorChooserDialog
ColorChooserWidget
ColorSelectionDialog
ComboBox
ComboBoxText
Dialog
DrawingArea
Entry
EntryCompletion
EventBox
Expander
FileChooserButton
FileChooserNative
FileChooserWidget
FileFilter
Fixed
FlowBox
FontButton
FontChooserDialog
FontChooserWidget
FontSelection
FontSelectionDialog
Frame
GLArea
Grid
HandleBox
HBox
HButtonBox
HeaderBar
HPaned
HScale
HScrollbar
HSeparator
IconView
ImageMenuItem
InfoBar
Invisible
Label
Layout
LevelBar
LinkButton
ListBox
ListBoxRow
ListStore
LockButton
Menu
MenuBar
MenuButton
MenuItem
MenuToolButton
ModelButton
Notebook
OffscreenWindow
Overlay
Paned
PlacesSidebar
Popover
PopoverMenu
ProgressBar
RadioButton
RadioMenuItem
RadioToolButton
RecentChooserMenu
RecentChooserWidget
RecentManager
Revealer
Scale
ScaleButton
Scrollbar
ScrolledWindow
SearchBar
SearchEntry
Separator
SeparatorMenuItem
SeparatorToolItem
ShortcutLabel
SizeGroup
SpinButton
Spinner
Stack
StackSidebar
StackSwitcher
Statusbar
Style
Switch
Table
TearoffMenuItem
TextView
ToggleButton
ToggleToolButton
Toolbar
ToolButton
ToolItem
ToolItemGroup
ToolPalette
TreeStore
TreeView
TreeViewColumn
VBox
VButtonBox
Viewport
VolumeButton
VPaned
VScrollbar
VSeparator
Window
"""
const GTK_SOURCE_EPI = """
proc getView(builder: Builder; name: string): View =
new result
let gt = g_type_from_name("GSource")
assert(gt != 0)
result.impl = gtk_builder_get_object(cast[ptr Builder00](builder.impl), name)
assert(toBool(g_type_check_instance_is_a(cast[ptr TypeInstance00](result.impl), gt)))
proc completionContextGetType*(): GType {.importc: "gtk_source_completion_context_get_type", libprag.}
proc completionProviderGetType*(): GType {.importc: "gtk_source_completion_provider_get_type", libprag.}
proc completionWordsGetType*(): GType {.importc: "gtk_source_completion_words_get_type", libprag.}
proc bufferGetType*(): GType {.importc: "gtk_source_buffer_get_type", libprag.}
proc completionGetType*(): GType {.importc: "gtk_source_completion_get_type", libprag.}
proc completionInfoGetType*(): GType {.importc: "gtk_source_completion_info_get_type", libprag.}
proc completionItemGetType*(): GType {.importc: "gtk_source_completion_item_get_type", libprag.}
proc completionProposalGetType*(): GType {.importc: "gtk_source_completion_proposal_get_type", libprag.}
proc encodingGetType*(): GType {.importc: "gtk_source_encoding_get_type", libprag.}
proc fileGetType*(): GType {.importc: "gtk_source_file_get_type", libprag.}
proc fileLoaderGetType*(): GType {.importc: "gtk_source_file_loader_get_type", libprag.}
proc fileSaverGetType*(): GType {.importc: "gtk_source_file_saver_get_type", libprag.}
proc gutterGetType*(): GType {.importc: "gtk_source_gutter_get_type", libprag.}
proc gutterRendererGetType*(): GType {.importc: "gtk_source_gutter_renderer_get_type", libprag.}
proc gutterRendererTextGetType*(): GType {.importc: "gtk_source_gutter_renderer_text_get_type", libprag.}
proc gutterRendererPixbufGetType*(): GType {.importc: "gtk_source_gutter_renderer_pixbuf_get_type", libprag.}
proc languageGetType*(): GType {.importc: "gtk_source_language_get_type", libprag.}
proc languageManagerGetType*(): GType {.importc: "gtk_source_language_manager_get_type", libprag.}
proc viewGetType*(): GType {.importc: "gtk_source_view_get_type", libprag.}
proc mapGetType*(): GType {.importc: "gtk_source_map_get_type", libprag.}
proc markGetType*(): GType {.importc: "gtk_source_mark_get_type", libprag.}
proc markAttributesGetType*(): GType {.importc: "gtk_source_mark_attributes_get_type", libprag.}
proc printCompositorGetType*(): GType {.importc: "gtk_source_print_compositor_get_type", libprag.}
proc regionGetType*(): GType {.importc: "gtk_source_region_get_type", libprag.}
proc searchContextGetType*(): GType {.importc: "gtk_source_search_context_get_type", libprag.}
proc searchSettingsGetType*(): GType {.importc: "gtk_source_search_settings_get_type", libprag.}
proc spaceDrawerGetType*(): GType {.importc: "gtk_source_space_drawer_get_type", libprag.}
proc styleGetType*(): GType {.importc: "gtk_source_style_get_type", libprag.}
proc styleSchemeGetType*(): GType {.importc: "gtk_source_style_scheme_get_type", libprag.}
proc styleSchemeChooserGetType*(): GType {.importc: "gtk_source_style_scheme_chooser_get_type", libprag.}
proc styleSchemeChooserButtonGetType*(): GType {.importc: "gtk_source_style_scheme_chooser_button_get_type", libprag.}
proc styleSchemeChooserWidgetGetType*(): GType {.importc: "gtk_source_style_scheme_chooser_widget_get_type", libprag.}
proc styleSchemeManagerGetType*(): GType {.importc: "gtk_source_style_scheme_manager_get_type", libprag.}
proc tagGetType*(): GType {.importc: "gtk_source_tag_get_type", libprag.}
proc undoManagerGetType*(): GType {.importc: "gtk_source_undo_manager_get_type", libprag.}
proc completionWordsProposalGetType*(): GType {.importc: "gtk_source_completion_words_proposal_get_type", libprag.}
proc completionWordsLibraryGetType*(): GType {.importc: "gtk_source_completion_words_library_get_type", libprag.}
proc completionWordsBufferGetType*(): GType {.importc: "gtk_source_completion_words_buffer_get_type", libprag.}
# register the GObject types so builder can use them, see
# https://mail.gnome.org/archives/gtk-list/2015-March/msg00016.html
discard viewGetType()
discard completionInfoGetType()
discard styleSchemeChooserButtonGetType()
"""
const GDK_EPI = """ const GDK_EPI = """
proc gdk_window_invalidate_nilrect(self: ptr Window00; rect: ptr Rectangle; invalidateChildren: gboolean) {. proc gdk_window_invalidate_nilrect(self: ptr Window00; rect: ptr Rectangle; invalidateChildren: gboolean) {.
@ -1419,6 +1675,60 @@ proc fixednewCursorFromName*(display: Display; name: string): Cursor =
#assert(g_object_get_qdata(result.impl, Quark) == nil) #assert(g_object_get_qdata(result.impl, Quark) == nil)
g_object_set_qdata(result.impl, Quark, addr(result[])) g_object_set_qdata(result.impl, Quark, addr(result[]))
proc getAngle*(self: SomeEvent; event2: SomeEvent): cdouble =
if not toBool(gdk_events_get_angle(cast[ptr Event00](self.impl), cast[ptr Event00](event2.impl), result)):
raise newException(Defect, "Events do not support getAngle().")
proc getCenter*(self: SomeEvent; event2: SomeEvent): (cdouble, cdouble) =
if not toBool(gdk_events_get_center(cast[ptr Event00](self.impl), cast[ptr Event00](event2.impl), result[0], result[1])):
raise newException(Defect, "Events do not support getCenter().")
proc getDistance*(self: SomeEvent; event2: SomeEvent): cdouble =
if not toBool(gdk_events_get_distance(cast[ptr Event00](self.impl), cast[ptr Event00](event2.impl), result)):
raise newException(Defect, "Events do not support getDistance().")
proc getAxis*(self: SomeEvent; axisUse: AxisUse): cdouble =
if not toBool(gdk_event_get_axis(cast[ptr Event00](self.impl), axisUse, result)):
raise newException(Defect, "Event does not support getAxis().")
proc getButton*(self: SomeEvent): int =
var button: uint32
if not toBool(gdk_event_get_button(cast[ptr Event00](self.impl), button)):
raise newException(Defect, "Event don't has a button field.")
return int(button)
proc getClickCount*(self: SomeEvent): int =
var clickCount_00: uint32
if not toBool(gdk_event_get_click_count(cast[ptr Event00](self.impl), clickCount_00)):
raise newException(Defect, "Event does not support getClickCount().")
return int(clickCount_00)
proc getCoords*(self: SomeEvent): (cdouble, cdouble) =
if not toBool(gdk_event_get_coords(cast[ptr Event00](self.impl), result[0], result[1])):
raise newException(Defect, "This event don't has a coordinate field.")
proc getKeyval*(self: SomeEvent): int =
var keyval_00 = uint32
if not toBool(gdk_event_get_keyval(cast[ptr Event00](self.impl), keyval_00)):
raise newException(Defect, "This event don't has a keyval field.")
return int(keyval_00)
proc getRootCoords*(self: SomeEvent): (cdouble, cdouble) =
if not toBool(gdk_event_get_root_coords(cast[ptr Event00](self.impl), result[0], result[1])):
raise newException(Defect, "This event don't has a rootCoordinate field.")
proc getScrollDeltas*(self: SomeEvent): (cdouble, cdouble) =
if not toBool(gdk_event_get_scroll_deltas(cast[ptr Event00](self.impl), result[0], result[1])):
raise newException(Defect, "This event don't has a scrollDeltas field.")
proc getScrollDirection*(self: SomeEvent): ScrollDirection =
if not toBool(gdk_event_get_scroll_direction(cast[ptr Event00](self.impl), result)):
raise newException(Defect, "Event don't has a scrollDirection field.")
proc getState*(self: SomeEvent): ModifierType =
if not toBool(gdk_event_get_state(cast[ptr Event00](self.impl), result)):
raise newException(Defect, "Event don't has a state field.")
""" """
const GIO_EPI = """ const GIO_EPI = """
@ -1801,12 +2111,28 @@ proc main(namespace: string) =
if namespace == "Gtk": if namespace == "Gtk":
output.write(GTK_EPI) output.write(GTK_EPI)
for i in GtkBuilderObjects.split:
if i == "": continue
output.write("""
proc get$1*(builder: Builder; name: string): $1 =
new result
let gt = g_type_from_name("Gtk$1")
assert(gt != 0)
result.impl = gtk_builder_get_object(cast[ptr Builder00](builder.impl), name)
assert(toBool(g_type_check_instance_is_a(cast[ptr TypeInstance00](result.impl), gt)))
""" % [i])
output.write("")
if namespace == "Gio": if namespace == "Gio":
output.write(GIO_EPI) output.write(GIO_EPI)
if namespace == "Gdk": if namespace == "Gdk":
output.write(GDK_EPI) output.write(GDK_EPI)
if namespace == "GtkSource":
output.write(GTK_SOURCE_EPI)
output.flush output.flush
var o = open("nim_gi" / (namespace.toLowerAscii) & ".nim", fmWrite) var o = open("nim_gi" / (namespace.toLowerAscii) & ".nim", fmWrite)
o.write(output.data) o.write(output.data)