[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 Considerations ** 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 trifle difficult: Both the stubs mechanism and the lifetime management of Tcl_Obj data structures rely on macros, and Fortran can not deal with C macros. That means that, at least for the moment, the string interface must suffice. ** 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 ====== To give you an idea, here is a screenshot: [Screenshot demo-ftk] ---- '''[arjen] - 2023-03-23 07:46:57''' I have now realised this example as a standalone Fortran program. Instead of the regular Tcl/Tk libraries I use the tclkit DLL from KitCreator. It took me a bit of work to iron out a somewhat mysterious bug (which with hindsight is completely understandable), but it definitely works. Now I need to clean up the code a bit. <> Deployment | Package | Fortran