A “Progress Bar” in AppGraphics

A user on our forums posted a question about creating a progress bar using AppGraphics. Because Fortran is often relied upon for long-running computations, a graphical progress bar could be very useful. While AppGraphics doesn’t provide a progress bar solution out of the box, creating one isn’t particularly difficult.

In it’s simplest form, a progress bar is really just a partially filled rectangle. With a little creativity, we can create a window that provides all the progress bar behaviors we might want. For maximum portability, we’ll assume that we want a standalone window that can display progress. Ideally, it should also provide:

  1. An optional “Cancel” button
  2. A way to display text
  3. A simple 0 to 100 percent update mechanism

For fun, we’ll also design our progress bar window to be Fortran-2003-esque using some type-bound procedures. To start, we need a derived type to describe our progress bar:

    type progresswindow

        integer::win
        integer::cancel
        
        character(128)::text
        
        contains
        
        procedure :: close => closeprogress
        procedure :: draw  => drawprogress
        
    end type progresswindow

The type includes integers that refer to our progress window, a possible cancel button, and the text we’re displaying above the progress bar. We’ve also attached two type-bound procedures: a “close” subroutine and a “draw” subroutine.

To initialize this window, we’ll basically just need to create the window and set up some drawing styles. An abbreviated summary is below:

        p%win = initwindow(400, 100, title=title, dbflag=.FALSE., closeflag=.FALSE.)
        call setbkcolor(systemcolor(COLOR_WINDOW_BKGD))
        call setcolor(BLACK)
        call settextstyle(WINDOWS_FONT, HORIZ_DIR, 10)
        call setfillstyle(SOLID_FILL, RED)

The above code creates a window, configures some colors and fonts to match Windows defaults, and sets up the fill style for a red progress bar.

The first type-bound procedure, close, is exceptionally simple, needing only to close this window, so we’ll skip that for now. In contrast, drawing the window is a bit more involved. We’ll need to take a number of steps. Our type definition pointed to a routine “drawprogress,” which we’ll define as such:

    subroutine drawprogress(p, completed, text)
    use appgraphics
    implicit none
    
        class(progresswindow), volatile::p
        integer, intent(in)::completed
        character(*), intent(in), optional::text

        ...

The drawing routine requires a completed percent, which is assumed to be 0 to 100, to be passed in. Optionally, the developer can also provide an updated text string to display above our progress bar. If the text is passed in, we’ll overwrite the text that our “progresswindow” variable is currently storing before we proceed with drawing.

First, we need to clear our viewport of existing graphics with an appropriate call:

    call setcurrentwindow(p%win)
    call clearviewport()

Note that prior to clearing the viewport, we’ve set the current AppGraphics window. This operation safeguards against applications where we may have multiple windows present. The developer, however, must be careful to switch windows back to any other windows when necessary.

Next, we can output the text into the window:

    if(len_trim(p%text) > 0) then
        call outtextxy(5, 5, trim(p%text))
    end if

If the text length is zero, we actually skip that step. Finally, we need to draw the progress bar. The actual progress bar really has two components: an empty rectangle and a filled rectangle whose size relative to the empty rectangle reflects the percent complete. AppGraphics doesn’t actually provide a quick routine for drawing unfilled rectangles, so we’ll instead use the relative line operations:

    progress_total = getmaxx() - 10
    
    ! Below we'll draw a simple box, unfilled
    call moveto(5, 25)
    call lineto(progress_total+5, 25)
    call lineto(progress_total+5, 50)
    call lineto(5, 50)
    call lineto(5, 25)

While the vertical coordinates aren’t particularly important, one should note that the empty rectangle should be about as wide as our window less five pixels on each side as a margin. Next, we can compute the width of filled rectangle based on what the developer passes into the drawing function:

    ! Bounds-check our completed value
    progress_completed = progress_total*completed/100
    if(progress_completed > progress_total) then
        progress_completed = progress_total
    elseif(progress_completed < 0) then
        progress_completed = 0
    end if

One additional step we’ve taken above is to ensure that the final width does not exceed the maximum width of our empty progress bar or that a negative with result from the user’s value. Once we have a width based on our completed progress, we can draw a filled bar:

    call bar(5, 25, progress_completed+5, 50)

This rather simple progress bar is actually quite effective. To test out how well it works, we can use a simplistic timing demo to watch the bar draw itself:

program main
use progress
implicit none

    type(progresswindow), volatile::p
    integer::counter
    real::last_time, now_time
    
    p = initprogress("Timed Progress")
    
    call cpu_time(last_time)
    now_time = last_time
    
    counter = 0
    
    do while(counter < 100) 
        
        do while(now_time - last_time < 0.2)
            call cpu_time(now_time)
        end do
        
        last_time = now_time
        counter = counter + 1
        
        call p%draw(counter)

    end do

    call p%close()

end program main

Because of our use of type-bound procedures, the actual driver routine is rather clean.

If you want to try out this simple progress bar, you can download the actual progress bar module and a slightly more advanced driver program (it implements a cancel button) below:

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s