Progress Bar (code snippet)

Description

Provides user feedback and cancel option for long running plugins.

Requires: iuplua

Function Prototype Version

This version uses a Function Prototype Closure to encapsulate the Global variables of the Global Variable Version, making them Private and Local, which has several benefits:

  • The variables cannot be accidentally accessed outside the function.
  • The variables have the run time performance of Locals.
  • The variables persist between function calls just like Globals.

Nevertheless, optionally some parameters can be held externally in a Global variable, without any loss of performance.

Function Prototype Closures are explained by Programming in Lua: Chapter 6.1 Closures.

Also this version offers several enhancements:

  • Improved display of both percentage progress and elapsed time clock.
  • Faster execution by updating the display only when necessary instead of every Step.
  • Defences against accidental misuse such as missing parameters, etc.
progressbar.fh_lua
--[[
@Title: Progress Bar (drop in)
@Author: Jane Taubman / Mike Tate
@LastUpdated: January 2013
@Description: Allows easy adding of a Progress Bar to any long running Plugin
]]
 
-- Progress Bar Function Prototype --
function NewProgressBar(tblGauge)
 
	local tblGauge	= tblGauge or {}				-- Optional table of external parameters
	local strFont	= tblGauge.Font		or nil			-- Font dialogue default is current font
	local strButton	= tblGauge.Button	or "255 0 0"		-- Button colour default is red
	local strBehind	= tblGauge.Behind	or "255 255 255"	-- Background colour default is white
	local intShowX	= tblGauge.ShowX	or iup.CENTER		-- Show window default position is central
	local intShowY	= tblGauge.ShowY	or iup.CENTER
	local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop
	local lblText, barGauge, lblDelta, btnStop, dlgGauge
 
	local function doFocus()					-- Bring the Progress Bar window into Focus
		dlgGauge.bringfront="YES"				-- If used too often, inhibits other windows scroll bars, etc
	end -- local function doFocus
 
	local function doUpdate()					-- Update the Progress Gauge and the Delta % with clock
		barGauge.value = intVal
		lblDelta.title = string.format("%4d %%      %s ",math.floor(intPercent),strClock)
	end -- local function doUpdate
 
	local function doReset()					-- Reset all dialogue variables and Update display
		intVal		= 0					-- Current value of Progress Bar
		intPercent	= 0.01					-- Percentage of progress
		intStart	= os.time()				-- Start time of progress
		intDelta	= 0					-- Delta time of progress
		intScale	= math.ceil( intMax / 1000 )		-- Scale of percentage per second (this guess is corrected in Step function)
		strClock	= "00 : 00 : 00"			-- Clock delta time display
		isBarStop	= false					-- Stop button pressed signal
		doUpdate()
		doFocus()
	end -- local function doReset
 
	local tblProgressBar = {
 
		Start = function(strTitle,intMaximum)			-- Create & start Progress Bar window
			if not dlgGauge then
				strTitle = strTitle or ""		-- Dialogue and button title
				intMax = intMaximum or 100		-- Maximun range of Progress Bar, default is 100
				local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30"	-- Adjust Stop button size to Title
				lblText  = iup.label	{ title=" ", expand="YES", alignment="ACENTER", tip="Progress Message" }
				barGauge = iup.progressbar { rastersize="400x30", value=0, max=intMax, tip="Progress Bar" }
				lblDelta = iup.label	{ title=" ", expand="YES", alignment="ACENTER", tip="Percentage and Elapsed Time" }
				btnStop  = iup.button	{ title=" Stop "..strTitle, rastersize=strSize, fgcolor=strButton, tip="Stop Progress Button", action=function() isBarStop = true end }	-- Signal Stop button pressed	return iup.CLOSE -- Often caused main GUI to close !!!
				dlgGauge = iup.dialog	{ title=strTitle.." Progress ", font=strFont, dialogframe="YES", background=strBehind,	-- Remove Windows minimize/maximize menu
								iup.vbox{ alignment="ACENTER", gap="10", margin="10x10",
									lblText,
									barGauge,
									lblDelta,
									btnStop,
								},
								move_cb = function(self,x,y) tblGauge.ShowX = x tblGauge.ShowY = y end,
								close_cb = btnStop.action,		-- Windows Close button = Stop button
							}
				dlgGauge:showxy(intShowX,intShowY)					-- Show the Progress Bar window
				doReset()								-- Reset the Progress Bar display
			end
		end,
 
		Message = function(strText)								-- Show the Progress Bar message
			if dlgGauge then lblText.title = strText end
		end,
 
		Step = function(intStep)								-- Step the Progress Bar forward
			if dlgGauge then
				intVal = intVal + ( intStep or 1 )					-- Default step is 1
				local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale
				if intPercent ~= intNew then						-- Update progress once per percent or per second, whichever is smaller
					intPercent = math.max( 0.1, intNew )				-- Ensure percentage is greater than zero
					if intVal > intMax then intVal = intMax intPercent = 100 end	-- Ensure values do not exceed maximum
					intNew = os.difftime(os.time(),intStart)
					if intDelta < intNew then					-- Update clock of elapsed time
						intDelta = intNew
						intScale = math.ceil( intDelta / intPercent )		-- Scale of seconds per percentage step
						local intHour = math.floor( intDelta / 3600 )
						local intMins = math.floor( intDelta / 60 - intHour * 60 )
						local intSecs = intDelta - intMins * 60 - intHour * 3600
						strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs)
					end
					doUpdate()							-- Update the Progress Bar display
				end
				iup.LoopStep()
			end
		end,
 
		Focus = function()
			if dlgGauge then doFocus() end							-- Bring the Progress Bar window to front
		end,
 
		Reset = function()									-- Reset the Progress Bar display
			if dlgGauge then doReset() end
		end,
 
		Stop = function()									-- Check if Stop button pressed
			iup.LoopStep()
			return isBarStop
		end,
 
		Close = function()									-- Close the Progress Bar window
			isBarStop = false
			if dlgGauge then dlgGauge:destroy() dlgGauge = nil end
		end,
 
	} -- end newProgressBar
	return tblProgressBar
end -- function NewProgressBar

Usage

Default Internal Parameters Example

	local ProgressBar = NewProgressBar()
	ProgressBar.Start( "My Task" )		-- Defaults to 100 steps maximum
	for intStep = 1, 100 do
		ProgressBar.Message( intStep .. " %" )
		fhSleep( 50, 40 )		-- Emulate performing the task 
		ProgressBar.Step()		-- Defaults to step by 1
		if ProgressBar.Stop() then
			break
		end
	end 
	ProgressBar.Close()

Advanced External Parameters Example

	local tblGauge = {			-- Pass parameters into Progress Bar function prototype
		Button	= "0 0 255",		-- Blue stop button
		ShowX	= 100,			-- Position window near top left
		ShowY	= 100,
		Font	= "Tahoma, Bold 10",	-- Tahoma, bold 10 point font
		}
	local ProgressBar = NewProgressBar(tblGauge)
	ProgressBar.Start( "My Task", 300 )	-- Set maximum range to 300
	for intStep = 1, 300 do
		ProgressBar.Message( "Step = " .. intStep )
		fhSleep( 20, 20 )		-- Emulate performing the task 
		ProgressBar.Step(1)
		if ProgressBar.Stop() then
			ProgressBar.Reset()
			fhSleep( 1500, 100 )	-- Emulate progress stopped action
			break
		end
	end 
	ProgressBar.Close()

Reducing Reverse Progress Bar Example

	local ProgressBar = NewProgressBar()
	ProgressBar.Start( "My Task", 100 )
	ProgressBar.Step(100)			-- Start with full bar
	for intStep = 1, 100 do
		ProgressBar.Message( 100 - intStep .. " %" )
		fhSleep( 50, 40 )		-- Emulate performing the task 
		ProgressBar.Step(-1)		-- Step backwards
		if ProgressBar.Stop() then
			break
		end
	end 
	ProgressBar.Close()

Global Variable Version

This version requires Global variables to communicate between the various functions.

progressbar.fh_lua
--[[
@Title: Progress Display (drop in)
@Author: Jane Taubman / Mike Tate
@LastUpdated: May 2012
@Description: Allows easy adding of a Progress Bar to any long running Plugin
]]
 
StrWhite = "255 255 255"
 
ProgressDisplay = {
 
	Start = function(strTitle,intMax)	-- Create and start the Progress Display window controls
		if not dlgProgress then
			cancelflag = false
			local cancelbutton = iup.button{ title="Cancel", rastersize="200x30", 
				action = function()
					cancelflag = true -- Signal that Cancel button was pressed 
					return iup.CLOSE
				end
			}
			gaugeProgress	= iup.progressbar{ rastersize="400x30", max=intMax }		-- Set progress bar maximum range
			messageline	= iup.label{ title=" ", expand="YES", alignment="ACENTER" } 
			dlgProgress	= iup.dialog{ title=strTitle, dialogframe="YES", background=StrWhite,	-- Remove Windows minimize/maximize menu
				iup.vbox{ alignment="ACENTER", gap="10", margin="10x10",
					messageline,
					gaugeProgress,
					cancelbutton
				}
			}
			dlgProgress.close_cb = cancelbutton.action	-- Windows Close button acts as Cancel button
			dlgProgress:showxy(iup.CENTER, iup.CENTER)	-- Show the Progress Display dialogue window
		end
	end,
 
	SetMessage = function(strMessage)	-- Set the progress message
		if dlgProgress then messageline.title = strMessage end
	end,
 
	Step = function(iStep)			-- Step the Progress Bar forward
		if dlgProgress then
			gaugeProgress.value = gaugeProgress.value + iStep
			local val = tonumber(gaugeProgress.value)
			local max = tonumber(gaugeProgress.max)
			if val > max then
				gaugeProgress.value = 0
			end
			iup.LoopStep()
		end
	end,
 
	Reset = function()			-- Reset progress bar
		if dlgProgress then gaugeProgress.value = 0 end
	end,
 
	Cancel = function()			-- Check if Cancel button pressed
		return cancelflag
	end,
 
	Close = function()			-- Close the dialogue window
		cancelflag = false
		if dlgProgress then dlgProgress:destroy() dlgProgress = nil end
	end,
 
}

Usage

Note the iup.LoopStep() function may also be needed in the main loop of the plugin.

ProgressDisplay.Start('This is my progress box',100)
for i=1,100 do
	ProgressDisplay.SetMessage(i.." %")
	fhSleep(50,40)	-- Emulate performing the task 
	ProgressDisplay.Step(1)
	if ProgressDisplay.Cancel() then
		break
	end
end 
ProgressDisplay.Reset()
ProgressDisplay.Close()