Multiple Windows with AppGraphics

A number of users have requested some further information about handling multiple windows in AppGraphics. While the Fortran interface certainly suggests a multi-window program is possible, the details of such a program might be a bit non-obvious. In this tutorial, we’ll walk through creating a multiple window application.

To start this exercise, we’ll need a primary window from which we’ll spawn children. While this design isn’t strictly necessary, it would be a common model for many users. Since we’ve gone over producing such windows in the past, we’ll just post the contents of a “main window” below:

program main
use appgraphics
use children, only: child_ids, child_create_time, redraw_children
implicit none

    integer::myscreen
    integer::create_child_button
    integer::sc_ticks
    integer::sc_tick_rate
    real::time_now
    
    myscreen = initwindow(200, 130, closeflag=.TRUE.)
    
    ! Add a button for creating children
    create_child_button = createbutton(60, 80, 80, 40, "Create Child", &
                                       handle_create_child)
    
    ! Initialize the child index arrays
    child_ids = -1
    child_create_time = 0.0

    ! Label this as the main window    
    call settextstyle(SANS_SERIF_FONT, HORIZ_DIR, 60)
    call outtextxy(15, 5, "Master")
    
    call system_clock(count_rate=sc_tick_rate)
    
    do while(.true.)
    
        ! Calculate the system time now in seconds
        call system_clock(count=sc_ticks)
        time_now = real(sc_ticks)/real(sc_tick_rate)

        ! Redraw all children and provide them with the system time
        call redraw_children(time_now)

        ! Set the master to the current window and idle 1 second
        call setcurrentwindow(myscreen)
        call startidle(1000)
                
    end do
    
    call closewindow(myscreen)
    
    contains
    
    subroutine handle_create_child()
    use children, only: create_child
    implicit none
    
        call create_child()
    
    end subroutine handle_create_child
    
end program main

Immediately, readers may notice an unfamiliar module, children, appearing at the beginning of this program. The children module will appear later in this writeup, and it will contain all the necessary subprograms for creating, managing, and destroying child windows.

In our main program, we first create a simple, single window that is labeled “Master” and has a single button labeled “Create Child.” Our goal is to spawn a child window whenever the user clicks said button.

After creating the window, we also initialize two arrays, child_ids and child_create_time, that are both used for managing child windows. The former array is used to maintain a list of the identifiers to all of our child windows. When a developer calls the createwindow function, a unique identifier to that window is returned; we need to explicitly keep track of these identifiers while our program is running to allow switching to and update child windows as necessary. The latter array just tracks the system clock time when each child window is created so that we’ll have something interesting to display in our child windows.

After all this initialization, our main program enters a simple loop. Within the loop, we’re calculating the system time in seconds and, using the time value, calling a subroutine to update all of our child windows. Once the update is complete, we carefully ensure the current window in AppGraphics is our main window and we idle for one second. Setting the current window is quite important in AppGraphics. Had we not set the current window to our master window prior to calling startidle, our idling loop would be applied to another window. Basically, as a rule of thumb in AppGraphics programs, any sequence calls to AppGraphics should be preceded by a call to setcurrentwindow to ensure the calls are being applied to the proper window. Without this step, it becomes quite easy to crash your program by attempting an operation, drawing, idling, or querying, on a window that no longer exists.

So far we’ve covered the easiest parts. Handling our child windows will be somewhat more complicated. To start, we’ll need to store some data within our children module:

module children
implicit none

    ! Allow no more than 32 windows
    integer, parameter::max_windows = 32
    
    ! Width and height of our child window
    integer, parameter::wc = 180
    integer, parameter::hc = 80
    
    ! Array of child window ids and the system times at which each was created
    integer, volatile, dimension(max_windows)::child_ids
    real, volatile, dimension(max_windows)::child_create_time
    
contains
...

We have a parameter that defines the maximum number of children we’ll allow; thirty two seems like a reasonable limit. We also define some parameters regarding drawing. The interesting data, though, are our two tracking arrays, child_ids and child_create_time. We’ve marked both arrays as “volatile” because there is a possibility the data in these arrays may be accessed by multiple threads.

Next, we need a subroutine to create a child window:

    subroutine create_child()
    use appgraphics
    implicit none
        
        integer::child_index
        character(40)::title
        integer::close_button
        
        ! Loop to find an available slot
        do child_index=1, max_windows
        
            ! If the array of children is a negative one, it is available for 
            ! storing a child id
            if(child_ids(child_index) .EQ. -1) then
            
                ! Create a title for this window
                Write(title, '(A5,1X,I3)') "Child", child_index
            
                ! Create the window, making sure that "closeflag" is false
                child_ids(child_index) = initwindow(wc, hc, &
                                                    title=title, &
                                                    closeflag=.FALSE.)
                
                ! Set up some styles for this window
                call settextstyle(WINDOWS_FONT, HORIZ_DIR, 8)
                call setbkcolor(BLACK)
                call setcolor(WHITE)
                
                ! Store a zero as "when it was created" for now
                child_create_time(child_index) = 0.0
                
                ! One button to close this child
                close_button = createbutton(wc-50, hc-40, 40, 30, &
                                            "Close", close_child)
                                            
                exit
                
            end if
        end do
    
    end subroutine create_child

This routine starts with a loop that looks through our child_ids array for an entry that is equal to -1. We’ll use a -1 to represent an empty slot in our child ids array; one might recall that we initialized the ray to-1 appropriately in our main program. When we do find a -1, we’ll proceed with a call to createwindow and store the new window’s identifier in the child_ids array. For fun we also create a title with the window’s index in the child_ids array. There is some basic graphical configuration of this new window as well. With our new window, we’ll save a creation time of 0.0 in the second array. Finally, we’ll add a close button.

Readers might notice that we’ve already violated a rule mentioned earlier concerning calling setcurrentwindow prior to calling a sequence of AppGraphics routines. The only reason we’ve skipped this step is that the createwindow function also implies setting the current window to the latest window.

For our “close” button, we’ve attached the button to a subroutine named close_child. This routine requires some consideration here:

    subroutine close_child
    use appgraphics
    implicit none
    
        integer::child_id
        integer::child_index
        
        ! This call determines the id of the window that executed this
        ! callback
        child_id = getsignallingwindow()
        
        ! And we'll retrieve theindex of this window in our global
        ! arrays
        child_index = index_from_id(child_id)
        
        ! Mark this chold window slot as available for a new child
        if(child_index .GT. -1) then
            child_ids(child_index) = -1
            child_create_time(child_index) = 0.0
        end if
        
        if(child_id .GT. -1) then
            call closewindow(child_id)
        end if
            
    end subroutine close_child

To close a child, we need to take a few steps. First, we need to determine in which window the close button was clicked. All of our child windows call this routine, so we have to determine which to act upon. The getsignallingwindow function can be used to determine the id of the window from which the callback was made. Once we have the id of the child window, we’ll call another custom function to determine the index of this child in the child_ids array. The index_from_id is a simple array search, so we’ll ignore it in this writeup, although it appears in the complete source code.

The reason we need the index in the child_ids array is to set the entry for said window in the array back to -1 so that the slot is once again available for creating a new window.

Finally, we can close our window with a call to closewindow, explicitly passing the id of the window we’re closing.

At this point, we do indeed have a program with multiple windows, but none of the children do anything. To make this exercise interesting, we’ll perform some updates to our children. One may recall that our main program called a subroutine redraw_children that accepted a time argument. This subroutine will subsequently be used to call a redraw routine for all our children:

    subroutine redraw_children(time_now)
    implicit none
    
        real, intent(in)::time_now
        integer::i
        
        do i = 1, max_windows
            call redraw_child(i, time_now)        
        end do
        
    end subroutine redraw_children

The above routine isn’t particularly interesting. For each possible window, it calls a far more interesting subroutine, redraw_child:

    subroutine redraw_child(child_index, time_now)
    use appgraphics
    implicit none
    
        integer, intent(in)::child_index
        real, intent(in)::time_now
        integer::child_id
        character(40)::text
        integer::longevity
        integer::previous_current

        ! Get the child id from our array
        child_id = child_ids(child_index)
        if(child_id .GE. 0) then
        
            ! If its create time is zero, store the system time now
            if(child_create_time(child_index) .EQ. 0.0) then
            
                child_create_time(child_index) = time_now
                longevity = 0

            ! Otherwise, calculate the difference in times
            else
            
                longevity = int(time_now - child_create_time(child_index))
            
            end if
            
            ! Write out how old this window is
            write(text, '(A19,1X,I4,1X,A7)') "I've been alive for", &
                                             longevity, &
                                             "seconds"
            
            !  Store the "current" window for good measure
            previous_current = getcurrentwindow()
            
            ! Select this window as current and display the text in the window
            call setcurrentwindow(child_id)
            call clearviewport()
            call outtextxy(10, 10, text)
            
            !  Reset the current window to its previous state
            call setcurrentwindow(previous_current)

        end if
    
    end subroutine redraw_child

In the redraw_child subroutine, we first check that the id available in the child_ids array is greater than or equal to zero. Recall that a -1 in this array represents “no window” in our scheme. If the id is valid, we’ll proceed with drawing our window.

First, we can calculate, based on the time passed in, how long our window has been open. If a 0.0 is stored in child_create_time, we’ll store the passed time and say that our window has existed for 0 seconds. Otherwise, we can compute how long the window has existed by determining the difference between the current time and the stored time. We’ll use this time to prepare a string to display that states how long the window has been opened.

We’re now ready to draw our window. First, we’ll set our current window appropriately since we’re about to draw. However, prior to doing so, we’ll actually save the current window id in case we were sloppy elsewhere so that we can reset the current window when we’re done. Next, we can clear the window with a call to clearviewport. Then, we can output our text to the window. Finally, before leaving our drawing routine, we’ll reset the current window to its original state like the responsible bookkeepers we should be.

The result of all these routines leads to an interesting example program:

Multiple Windows

While this example might seem complex, users can hopefully decipher all the steps taken. Furthermore, creating muk\ltiple window applications is an inherently complex process, and we’ve tried to make such development as simple as possible.

The source code for the example appears below: