[Arjen Markus] (29 january 2023) I have been working on an interface
library for building GUIs with Tcl/Tk and Fortran. In its current state it is proof of concept,
not a full-fledged library. While only some basic features have been implemented, it does show
the possibilities of leveraging Tcl/Tk for this purpose.
The full code can be found at https://github.com/arjenmarkus/ftk.
** Some cConsiderations **
The philosophy of the interface is that much can be done via _evaluating_ Tcl commands, rather
than calling the Tcl API directly. Of course, this means that some roundabout processing is required:
building the string representing the command first in Fortran and then having it evaluated by Tcl/Tk.
Still, for the purpose of setting up the GUI this is fast enough and it saves detailed interfacing
between C and Fortran.
Another consideration of this interface is that much if not all can be done via Fortran directly,
thanks to the standardised C-Fortran binding. That does mean that some things are a trifledifficult: Both the stubs mechanism relies oand macros, just as the lifetime management forf Tcl_Obj
data
structures rely on macros, and Fortran can not deal with C macros. That means
that, at least for the
moment, the insterfacing relies on therface mustring intersufafice.
** Demo **
The project contains a single demo program. It needs to be built as a library that can be
loaded by Tcl/Tk's wish program. The sample build script demonstrates this.
A convenient aspect of simply loading a library is that the Fortran part does not need to
initialise the interpreter.
''Note:'' I have not paid much attention to the layout yet, so the rows
are quite far apart. This has to be improved, of course, but I wanted
to present this little project.
''Note:'' The Fortran code below is not FORTRAN 77, it uses
several modern features, such as the standardised C binding and
polymorphic variables.
The code for the demo program is shown below:
======
! demo_gui.f90 --
! Very simple GUI to show that the basics are working:
! It presents a column of three variables, start, stop and colour,
! as well as two buttons, one "Draw" and one "Exit" and a canvas.
! When you press "Draw", it draws a circle sector in the canvas
! using the given colour. Any previous sector is removed first.
!
! The GUI is starting from the start_gui routine - the name is
! fixed and it should appear outside any modules.
!
subroutine start_gui
use ftk_module
implicit none
!
! Widgets we create: they must be saved!
!
character(len=:), allocatable, save :: canvas, title_label, param_label, &
label_1, entry_1, &
label_2, entry_2, &
label_3, entry_3, &
button_frame, draw_button, exit_button
type(ftk_option), dimension(3) :: option
!
! Set up the window:
!
! --- Text to explain the GUI ----
! Parameters: +-----------------------------+
! Start: [ ] | |
! Stop: [ ] | |
! Colour: [ ] | |
! | |
! | |
! | |
! | |
! +-----------------------------+
! [Draw] [Exit]
!
!
! Open the console, for debugging and so on
!
call ftcl_evaluate( "console show" )
canvas = ftk_create_widget( default_toplevel, ftk_canvas, "canvas" )
option(1) = ftk_option( "-text", "Draw a circle sector in a given colour" )
option(2) = ftk_option( "-text", "Parameters:" )
title_label = ftk_create_widget( default_toplevel, ftk_label, "title", option(1:1) )
param_label = ftk_create_widget( default_toplevel, ftk_label, "param", option(2:2) )
option(1) = ftk_option( "-text", "Start angle:" )
option(2) = ftk_option( "-text", "Stop angle:" )
option(3) = ftk_option( "-text", "Colour:" )
label_1 = ftk_create_widget( default_toplevel, ftk_label, "label_1", option(1:1) )
label_2 = ftk_create_widget( default_toplevel, ftk_label, "label_2", option(2:2) )
label_3 = ftk_create_widget( default_toplevel, ftk_label, "label_3", option(3:3) )
option(1) = ftk_option( "-textvariable", "start" )
option(2) = ftk_option( "-textvariable", "stop" )
option(3) = ftk_option( "-textvariable", "colour" )
entry_1 = ftk_create_widget( default_toplevel, ftk_entry, "entry_1", option(1:1) )
entry_2 = ftk_create_widget( default_toplevel, ftk_entry, "entry_2", option(2:2) )
entry_3 = ftk_create_widget( default_toplevel, ftk_entry, "entry_3", option(3:3) )
button_frame = ftk_create_widget( default_toplevel, ftk_frame, "frame" )
option(1) = ftk_option( "-text", "Draw" )
option(2) = ftk_option( "-text", "Exit" )
draw_button = ftk_create_widget( button_frame, ftk_button, "draw", option(1:1) )
exit_button = ftk_create_widget( button_frame, ftk_button, "exit", option(2:2) )
call ftk_grid_add_row( [draw_button, exit_button] )
call ftk_grid_add_row( [title_label, ftk_add_left, ftk_add_left] )
call ftk_grid_add_row( [param_label, ftk_add_left, canvas] )
call ftk_grid_add_row( [label_1, entry_1, ftk_add_top] )
call ftk_grid_add_row( [label_2, entry_2, ftk_add_top] )
call ftk_grid_add_row( [label_3, entry_3, ftk_add_top] )
call ftk_grid_add_row( [button_frame, ftk_add_left, ftk_add_left] )
!
! Initialise
!
call ftcl_evaluate( "set start 60" )
call ftcl_evaluate( "set stop 180" )
call ftcl_evaluate( "set colour red" )
!
! Add the commands
! TODO:
! Find out why we need to have a dummy first entry. It is bizarre.
!
call ftk_button_add_command( ".a" , do_not_use, "" )
call ftk_button_add_command( draw_button, draw_sector, "" )
call ftk_button_add_command( exit_button, stop_program, "" )
! TODO: the rest
!
! SImply return, the event loop takes over
!
contains
subroutine draw_sector( dummy )
class(*) :: dummy
integer :: start, stop
character(len=:), allocatable :: colour
character(len=200) :: string
call ftcl_getvar( "start", start )
call ftcl_getvar( "stop", stop )
call ftcl_getvar( "colour", colour )
if ( colour == "" ) then
colour = "white"
endif
call ftcl_evaluate( canvas // " delete all" )
write( string, * ) canvas, " create arc ", 10, 10, 210, 210, " -start ", start, " -extent ", stop-start, " -fill ", colour
call ftcl_evaluate( string )
end subroutine draw_sector
subroutine stop_program( dummy )
class(*) :: dummy
stop
end subroutine stop_program
!
! I do not know why it is necessary to register an extra routine, but if
! I do it like this, the program works fine.
!
subroutine do_not_use( dummy )
class(*) :: dummy
end subroutine do_not_use
end subroutine start_gui
======
You can build the library like this (just an example - you can also use
the gfortran compiler and you porbably need to change the path to the
Tcl library):
======
rem The stubs library is not useable!
ifort -c ftcl_mod_interp.f90
ifort -c ftcl_variables.f90
ifort -c ftk_module.f90
ifort demo_gui.f90 ftk_module.obj ftcl_variables.obj ftcl_mod_interp.obj -exe:ftk.dll c:\ActiveTcl\lib\tcl86t.lib -dll
======
Finally, to run it via `wish`:
======
# test_ftk.tcl --
# Test the Ftk module
#
load ftk.dll
======
<<categories>> Deployment | Package | Fortran