Page 1 of 1

Procedure to Enable/Disable Buttons

Posted: Mon Nov 17, 2003 2:26 pm
by akj
Code updated to 5.20

When I write PureBasic programs I tend to use buttons rather than menus. This can mean a dozen or more buttons each of which needs to be suitably enabled or disabled according to whether the user is allowed to click the button or not. This can be a tedious to code.

To simplify the enabling/disabling of buttons I have written a procedure called EnableButtons() which very concisely can change the state of up to 26 buttons.

You may think this application rather trivial, but it is a great time-saver for me and may be of use to others.

Below is the code for a demonstration program and the code for the EnableButtons() procedure. The latter is rather over-commented so looks much larger than it really is.

Code: Select all

; Button Demo  AKJ  17-Nov-03
; This program demonstrates the EnableButtons() procedure.
; It does nothing more than enabling/disabling buttons in a fixed sequence.
; Keep pressing the 'Next' button to cycle through the sequence.
; Click the 'Exit' button to prematurely terminate the program.

Enumeration
	#window
	#butOpen ; O
	#butSave ; S
	#butClos ; C
	#butNext ; N
	#butExit ; X The last button must be named #butExit
	#listbox
EndEnumeration
#butLAST = #butExit ; The last button, usually the exit button
#butLIST$ = "OSCNX" ; List of button codes in the same order as the buttons

Declare EnableButtons(buttons$)

; Draw GUI interface
bord=30 ; Border width
butw=100: buth=50: butx=bord: buty=bord ; Button metrics
col1w = 24: col2w=butw ; Listbox column widths
lboxw=col1w+col2w+4; Listbox width (The +4 is a kludge to suppress the h. scrollbar)
winw=bord+butw+bord+lboxw+bord ; Window width
winh=bord+(buth+bord)*Len(#butLIST$)

If OpenWindow(#window,0,0,winw,winh,"Button Demo:  Keep clicking the Next button",#PB_Window_ScreenCentered)=0
	MessageRequester("Error","Failed to open window")
EndIf

UseGadgetList(WindowID(#window))
ButtonGadget(#butOpen, bord, buty, butw, buth, "Open") : buty+buth+bord
ButtonGadget(#butSave, bord, buty, butw, buth, "Save") : buty+buth+bord
ButtonGadget(#butClos, bord, buty, butw, buth, "Close") : buty+buth+bord
ButtonGadget(#butNext, bord, buty, butw, buth, "Next (Click_Me)",#PB_Button_MultiLine|#PB_Button_Default) : buty+buth+bord
ButtonGadget(#butExit, bord, buty, butw, buth, "Exit")
ListIconGadget(#listbox,bord+butw+bord,bord,lboxw,winh-bord*2,"->",col1w) ; Listbox
AddGadgetColumn(#listbox,1,"Code",col2w)

; Populate listbox
AddGadgetItem(#listbox,-1,"__"+Chr(10)+"Initial state")
Read codecount

For i=1 To codecount
	Read.s code$
	AddGadgetItem(#listbox,-1,Chr(10)+code$)
Next i

; Main loop
Restore codes
codecount=1

Repeat
	If WaitWindowEvent()=#PB_Event_Gadget And EventGadget()=#butNext
		SetGadgetItemText(#listbox,codecount-1,"",0) ; Move the pointer
		SetGadgetItemText(#listbox,codecount,"__",0)
		codecount+1
		Read.s code$
		EnableButtons(code$) ; Change the state of the buttons
	EndIf
Until EventGadget()=#butExit
CloseWindow(#window)
End

DataSection
	Data.l 10 ; Number of code strings below
	codes:
	Data.s "$NX-", "O", "-S", "-C", "c", "=", "=", "=", "+", "0X"
	; The code "$NX-" declares 'Next' and 'Exit' buttons as sticky,
	; enables those buttons and then disables all other buttons.
	; Sticky buttons are unaffected by codes "+" and "-".
EndDataSection

Code: Select all

Procedure EnableButtons(buttons$) ; AKJ  17-Nov-03
	; Enable or disable up to 26 buttons using the code string in buttons$.
	; The buttons must have sequential gadget numbers without any gaps.
	
	; Before calling this procedure for the first time:-
	; #butLAST must contain the gadget number of the last (usually 'Exit') button.
	; #butLIST$ must contain a list of all assigned button codes,
	;   in the same order as the button gadget numbers.
	;   Each button code is any single UPPERCASE letter, unique to that button.
	
	; Example button gadget numbers: (Must be consecutive and in the main program):
	;   Enumeration ; Does not have to start at zero
	;     #butOpen  ; "O" ; "O" is the button code assigned to the Open button
	;     #butClose ; "C"
	;     #butExit  ; "X"
	;   EndEnumeration
	; Required by EnableButtons():-
	;   #butLAST = #butExit
	;   #butLIST$ = "OCX" ; UPPERCASE letters in the same order as the buttons
	
	; Codes in button$ on calling this procedure:
	;   "A"  (uppercase letter[s]) Enable the associated button[s]  E.g. "OC".
	;   "a"  (lowercase letter[s]) Disable the associated button[s]  E.g. "co".
	;   "?A" or "?a" Toggle enable/disable for the associated button[s] E.g. "?C" or "?oc".
	;        It is irrelevant whether the letters are upper- or lower-case.
	;   "$A" or "$a" Designate the associated button[s] as sticky (see "+" and "-").
	;        Also enable/disable the associated button[s] as if the $ was not present.
	;        Useful when some buttons rarely change state, such as "About" And "Exit".
	;        If no letters immediately follow the $, no buttons will be sticky.
	;        Initially, no buttons are sticky.
	;   "+"  Enable all non-sticky buttons
	;   "-"  Disable all non-sticky buttons
	;   "*"  Enable ALL buttons, including sticky buttons
	;   "0"  Disable ALL buttons, including sticky buttons
	;   "="  Pop the state of ALL buttons and make them current  E.g. "==="
	;   "|"  End the scope of "?" and "$" (see the example below)
	;        Actually, any valid non-alphabetic code will end the scope of "?" and "$".
	
	; Codes $ + - * 0 = normally occur only at the start of buttons$
	; Invalid codes are ignored.
	; Sticky buttons are unaffected by codes "+" and "-".
	
	; Examples:
	; 1. EnableButtons("$OX-") declares O and X as a sticky buttons, enables them and
	;    then disables all other buttons.
	; 2. EnableButtons("$X|O") declares X as a sticky button then enables X and O.
	; 3. EnableButtons("-C") disables all non-sticky buttons then enables button C.
	; 4. EnableButtons("=c?Xo") retrieves the previous state for all buttons,
	;    then disables C and toggles the enable/disable states of X and O.
	
	; Stack pushes are automatic (the post-states of the buttons are stacked)
	; N.B. The first call to EnableButtons() assumes all buttons are currently enabled
	
	; Convention: All variables are either a single letter or are prefixed by "but"
	Shared butstack$ ; An empty stack (as it is initially) assumes all buttons are enabled
	Shared butsticky$ ; List of sticky buttons, initially empty
	butmenu$="+-*0=?$|" ; Special codes
	s=#False ; Sticky button mode is off
	t=#False ; Button toggle mode is off
	w=Len(#butLIST$) ; Each stack entry is w+1 bytes long
	If buttons$=""
		buttons$="-" ; Disable all non-sticky buttons if the string is null
	EndIf
	; Get the current state (pre-state) of the buttons (hopefully as they really are)
	If Len(butstack$)>w ; If the stack is non-empty
		butprestate$=Left(butstack$,w) ; Current state of the buttons
	Else
		butprestate$=#butLIST$ ; Assume all buttons are currently enabled
	EndIf
	; Count the number of pop requests ("=")
	n=-1 : p=0
	Repeat
		n+1: p=FindString(buttons$, "=", p+1)
	Until p=0
	; Process any pop requests
	If n
		If Len(butstack$)>w ; If the stack is non-empty
			butstack$=Mid(butstack$,w+2,999) ; Drop the top-of-stack item
		EndIf
		; Retrieve an earlier button state from stack entry n
		If Len(butstack$)>=n*(w+1) ; If there are >=n entries on the stack
			; Pop button state from entry n
			buttons$=Mid(butstack$,n*w+n-w,w)+buttons$ ; Enhance the request list
			butstack$=Mid(butstack$,n*w+n+1,999) ; Remove stack entries 1..n
		Else
			buttons$="*"+buttons$ ; Assume all buttons were enabled if the stack is too small
			butstack$="" ; Remove all (<n) stack entries
		EndIf
	EndIf ; n
	; Determine the post-state of the buttons by processing any non-pop requests
	butstate$=butprestate$ ; Initialise the post-state
	For p=1 To Len(buttons$)
		c$=Mid(buttons$,p,1)
		If FindString(butmenu$,c$,1)
			t=#False ; Turn off toggle mode
			s=#False ; Turn off sticky mode
		EndIf
		Select c$
			Case "+" ; Enable all non-sticky buttons
				x$=butstate$
				butstate$=#butLIST$ ; Result if no buttons are sticky
				For q=1 To Len(butsticky$)
					d$=LCase(Mid(butsticky$,q,1))
					r=FindString(x$,d$,1)
					If r ; If sticky letter was originally lower case
						butstate$=Left(butstate$,r-1)+d$+Mid(butstate$,r+1,999) ; Reinstate the original
					EndIf
				Next q
			Case "-" ; Disable all non-sticky buttons
				x$=butstate$
				butstate$=LCase(#butLIST$) ; Result if no buttons are sticky
				For q=1 To Len(butsticky$)
					d$=UCase(Mid(butsticky$,q,1))
					r=FindString(x$,d$,1)
					If r ; If sticky letter was originally upper case
						butstate$=Left(butstate$,r-1)+d$+Mid(butstate$,r+1,999) ; Reinstate the original
					EndIf
				Next q
			Case "*" ; Enable all buttons
				butstate$=#butLIST$
			Case "0" ; Disable all buttons
				butstate$=LCase(#butLIST$)
			Case "=" ; Pop the button stack
				; Do nothing (already done)
			Case "?" ; Toggle any following buttons
				t=#True ; Turn on toggle mode
			Case "$" ; Designate following buttons as sticky
				butsticky$=""
				s=#True ; Turn on sticky mode
			Default ; Invalid code or (A..Z or a..z) Button code letter
				q=FindString(#butLIST$,UCase(c$),1)
				If q ; If valid
					If s ; If in sticky mode
						butsticky$ + c$
					EndIf
					If t ; If in toggle mode
						If Mid(butstate$,q,1)=UCase(c$) ; If button is currently on
							c$=LCase(c$) ; Turn it off
						Else
							c$=UCase(c$) ; Turn it on
						EndIf
					EndIf
					butstate$=Left(butstate$,q-1)+c$+Mid(butstate$,q+1,999)
				EndIf
		EndSelect
	Next p
	; Push the post-state of the buttons on to the stack
	butstack$=butstate$+"~"+butstack$ ; Stack entries are separated by "~"
	; Kludge to keep the stack size managable:
	; Truncate the stack if the fourth entry or later has all buttons enabled
	p=FindString(butstack$,#butLIST$,4*w+4)
	If p
		butstack$=Left(butstack$,p-1)
	EndIf
	; Implement required button state changes
	g=#butLAST-w ; Button gadget number
	For p=1 To w
		d$=Mid(butstate$,p,1) ; Button post-state
		c$=Mid(butprestate$,p,1) ; Button pre-state
		If d$<>c$ ; If a button needs to change state
			If UCase(d$)=d$
				DisableGadget(g+p, #False) ; If uppercase, enable button
			Else
				DisableGadget(g+p, #True) ; If lowercase, disable button
			EndIf
		EndIf
	Next p
EndProcedure

Posted: Mon Nov 17, 2003 2:35 pm
by akj
I have spotted a minor error in the demo program.

The line:

Code: Select all

#butExit ; X The last button must be named #butExit
should read:

Code: Select all

#butExit ; X