ExpanderGadget (for Linux/Windows & maybe Mac))

Share your advanced PureBasic knowledge/code with the community.
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

ExpanderGadget (for Linux/Windows & maybe Mac))

Post by walker »

Some applications are using something called ExpanderWidget (or Gadget in terms of PB) Here's my attempt to create such a gadget with PB only command-set...
which isn't possible for 100% as on Windows the behavior of gadgets is a little different. and there (on Windows) it has some glitches... I hope someone with more Win-API knowledge
can help to solve the problems.... But also everybody is invited to help to improve this gadget for the benefit of the PB-Community :D
Maybe someone with a Mac (Michel51?) could test this ....
A small explanation is included at the top
The point where the code needs to be changed (Windows) is located at line 265. The small api I'd implemented helps a bit to minimize the glitches on Windows...
but if your mouse is over the checkboxgadget created outside of the Expander... it will pop up into the foreground...:roll:
On Linux It's working like a charm.... :cool:

The code

Code: Select all

;- ExpanderGadget
;  2008 by walker
; 
;--------------------------------------------
;-free for everyone and any purposes
;-enhancements made by others
;-must be published and posted in the same PB-Forum ;-)
;-cross plattform ability must be achieved
;---Instructions---------------------------
; first set up a variable with 'expander' as type and fill in some parameter (those marked with an * are a must; 
;  those marked with # are set automatically - DO NOT SET OR CHANGE THESE VALUES) or use ExpanderSetDafaults(@myexpander)
; parameters:
;#   gadget_id               = gadgetnumber from #pb_any after creation of the gadget
;*    normal_image        = image shown at first start (loadimage/catchimage/createimage can be used)
;*    expanded_image   = image shown if clicked and gadget will be "expanded" (loadimage/catchimage/createimage can be used)
;*    nr                            = number of the gadget; to be used with its_gadget() function call
;     backgroundcolor.l   = color of the gadget's background - if you don't want to set an own color, you MUST set it to #pb_any
;     fontnr.l                     = font to be used with this gadget (loadfont())
;*    expandergroup.l      = to identify goups of Expandergadgets... see example
;*    gx                           = x-coordinate of the top left edge
;*    gy                           = y-coordinate of the top left edge
;*    gw                           = width of the gadget
;*    gh                           =height of the gadget
;#   container.l             = container for the expander
;     containerw.l             = calculated... but can be changed   
;     containerh.l             = calculated... but can be changed
;
; The next step is to create an ExpanderGadget with 'ExpanderGadget(@myExpanderGadget)'
; after this, add some gadgets to the  previous created Expander with AddExpanderGadget(@myExpanderGadget.expander,Gadget_number,Type,x,y,width,height,text.s="",color=0
; where Type is one of 'optiongadget', 'checkboxgadget', 'textgadget' or 'hyperlinkgadget'
; and at least close this gadget with 'CloseExpanderGadget(@myExpanderGadget)'
;
;---Structures----------------------------
;{
Structure expander_gadget ; structure for each gadget within an ExpanderGadget
    expander_gadget.l ; the parent expander gadget
    gadget_id.l             ; the gadgetid
    nr.l                         ;the number of the gadget
    gadgettype.s          ;type of the gadget
    gadgettext.s             ; text for the gadget (optional)
    color.l                       ;optional
    gx.l                          ;x-coordinate of the gadget
    gy.l                          ;y-coordinate of the gadget
    gw.l                          ;width of the gadget
    gh.l                         ;heigth of the gadget
EndStructure

Structure all_exp
    gadget_id.l
    group.l
EndStructure

Global NewList expander_gadgets.expander_gadget()

Structure expander  ;structure for the ExpanderGadget
    Gadget_id.l             ;Gadgetnumber 
    normal_image.l      ;imagenumber
    expanded_image.l  ;imagenumber
    backgroundcolor.l   ;color - if you don't want to set an own color, you MUST set it to #pb_any
    textcolor.l                ;color
    gadgettext.s            ;text
    fontnr.l                    ;font-number
    expandergroup.l      ;
    gx.l                          ;x-coordinate of the gadget
    gy.l                          ;y-coordinate of the gadget
    gw.l                          ;width of the gadget
    gh.l                         ;heigth of the gadget
    container.l               ;container for the expander
    containerw.l
    containerh.l
EndStructure

Global NewList all_expander_gadgets.all_exp()
;}
;---internal procedures-----------------
  
Procedure _create_expander_image(*Gadget_.expander,state); to create the appropriate image
Select state
    Case 1
        img_to_use=*Gadget_\normal_image
    Case 2
        img_to_use=*Gadget_\expanded_image
EndSelect

If ImageWidth(img_to_use)>ImageHeight(img_to_use)
    f=ImageWidth(img_to_use)/ImageHeight(img_to_use)
Else
    f=ImageHeight(img_to_use)/ImageWidth(img_to_use)
EndIf   
ResizeImage(img_to_use,*Gadget_\gh*f,*Gadget_\gh)

img_tmp=CreateImage(#PB_Any,*Gadget_\gw,*Gadget_\gh)

;determine backgroundcolor of the image
If *Gadget_\backgroundcolor=#PB_Any
    StartDrawing(ImageOutput(img_to_use))
    *Gadget_\backgroundcolor=Point(0,0)
    StopDrawing()
EndIf   
StartDrawing(ImageOutput(img_tmp))
DrawingFont(FontID(*Gadget_\fontnr))
FillArea(0,0,-1,*Gadget_\backgroundcolor)
DrawImage(ImageID(img_to_use),0,0)
DrawingMode(1)
DrawText(ImageWidth(img_to_use)+4,*Gadget_\gh/2-TextHeight(*Gadget_\gadgettext)/2,*Gadget_\gadgettext,*Gadget_\textcolor)
StopDrawing()
ProcedureReturn img_tmp
EndProcedure

Procedure _show_gadgets(*gadget_.expander,show.l); shows or hides all gadgets
ForEach expander_gadgets()
    If *gadget_\Gadget_id=expander_gadgets()\expander_gadget
        If *gadget_\container=#PB_Any   ;if not created yet...            
            just_new=#True
            ;the following is workin on Linux... perfectely... but not on windows (gadgets below the container are raised if the mouse is over them....)
            *gadget_\container=ContainerGadget(#PB_Any,*Gadget_\gx-2,*Gadget_\gy+*Gadget_\gh,*gadget_\containerw,*gadget_\containerh,#PB_Container_Double)          
        EndIf         
        
        Select LCase(expander_gadgets()\gadgettype)
            Case "optiongadget"
                If expander_gadgets()\gadget_id=#PB_Any 
                    expander_gadgets()\gadget_id=OptionGadget(#PB_Any,expander_gadgets()\gx,expander_gadgets()\gy,expander_gadgets()\gw,expander_gadgets()\gh,expander_gadgets()\gadgettext)
                EndIf   
            Case "checkboxgadget"
                If expander_gadgets()\gadget_id=#PB_Any 
                    expander_gadgets()\gadget_id=CheckBoxGadget(#PB_Any,expander_gadgets()\gx,expander_gadgets()\gy,expander_gadgets()\gw,expander_gadgets()\gh,expander_gadgets()\gadgettext)
                EndIf   
            Case "stringgadget"
                If expander_gadgets()\gadget_id=#PB_Any 
                    expander_gadgets()\gadget_id=StringGadget(#PB_Any,expander_gadgets()\gx,expander_gadgets()\gy,expander_gadgets()\gw,expander_gadgets()\gh,expander_gadgets()\gadgettext)
                EndIf               
             Case "textgadget"
                If expander_gadgets()\gadget_id=#PB_Any 
                    expander_gadgets()\gadget_id=TextGadget(#PB_Any,expander_gadgets()\gx,expander_gadgets()\gy,expander_gadgets()\gw,expander_gadgets()\gh,expander_gadgets()\gadgettext)
                EndIf                    
             Case "hyperlinkgadget"
                If expander_gadgets()\gadget_id=#PB_Any 
                    expander_gadgets()\gadget_id=HyperLinkGadget(#PB_Any,expander_gadgets()\gx,expander_gadgets()\gy,expander_gadgets()\gw,expander_gadgets()\gh,expander_gadgets()\gadgettext,expander_gadgets()\color)
                EndIf                    
            ; could be extended with more gadgets... if needed
                
        EndSelect                         
    EndIf
Next
If just_new=#True
    CloseGadgetList()
EndIf        
If show=#True           
    HideGadget(*gadget_\container,#False)              
    SetGadgetData(*Gadget_\Gadget_id,*gadget_\expanded_image)
    tmp_img=_create_expander_image(*gadget_,2)        
    SetGadgetState(*Gadget_\gadget_id,ImageID(tmp_img))
    FreeImage(tmp_img)
Else
    HideGadget(*gadget_\container,#True)   
    SetGadgetData(*Gadget_\Gadget_id,*gadget_\normal_image)
    tmp_img=_create_expander_image(*gadget_,1)        
    SetGadgetState(*Gadget_\gadget_id,ImageID(tmp_img))
    FreeImage(tmp_img)      
EndIf    
While WindowEvent():Wend
ProcedureReturn 1
EndProcedure

;---public procedures-------------------

Procedure ExpanderSetDefaults(*Gadget_.expander)

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    *background = GetSysColor_(#COLOR_WINDOW)
    *Gadget_\fontnr=LoadFont(#PB_Any,"Arial",10)  
CompilerEndIf   
;-TODO: determine the background color of a gtk window OR setting background to transparent
CompilerIf #PB_Compiler_OS = #PB_OS_Linux 
    *background=$F6F6F6
    *Gadget_\fontnr=LoadFont(#PB_Any,"Sans",10)
CompilerEndIf   
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
    *background = $CFF6FD; just a guess --- how to get the background on a MAC?
    *Gadget_\fontnr=LoadFont(#PB_Any,"Arial",10)
CompilerEndIf   
*gadget_\backgroundcolor=*background
For img = 0 To  1
    tmp_img=CreateImage(#PB_Any,16,16)
    StartDrawing(ImageOutput(tmp_img))
    FrontColor(*background)
    Box(0,0,16,16)
    If img = 0
        Start=4 
        For a = 7 To 11
            LineXY(Start,a-4,start,15-start,0)
            Start+1
        Next a
        StopDrawing()
        *Gadget_\normal_image=tmp_img                    
    ElseIf img=1
        Start=2 
        For a = 6 To 10
            Line(Start,a,13-Start*2,0,0)
            Start+1
        Next a
        StopDrawing()                
        *Gadget_\expanded_image=tmp_img
    EndIf   
Next
ProcedureReturn 1
EndProcedure

Procedure its_Gadget(gadgetid) ; check and return which gadget is processed 
ForEach expander_gadgets()
    If expander_gadgets()\nr=Gadgetid
        ProcedureReturn expander_gadgets()\gadget_id
    EndIf   
Next
ProcedureReturn 0
EndProcedure

Procedure CloseExpanderGadget(*gadget_.expander); NECESSARY after adding all gadgets to an ExpanderGadget
;if not called, the gadgets weren't created !!!!!
maxw=0
maxh=0
maxy=0
maxx=0
ForEach expander_gadgets()
    If *gadget_\Gadget_id=expander_gadgets()\expander_gadget
        If maxw<expander_gadgets()\gw
            maxw=expander_gadgets()\gw
        EndIf
        If maxy<expander_gadgets()\gy
            maxy=expander_gadgets()\gy
        EndIf
        If maxh<expander_gadgets()\gh
            maxh=expander_gadgets()\gh
        EndIf
        If maxx<expander_gadgets()\gx
            maxx=expander_gadgets()\gx
        EndIf
    EndIf
Next
*gadget_\containerw=maxw+30
*gadget_\containerh=maxy+maxh*2
_show_gadgets(*gadget_,#False)

ProcedureReturn 1
EndProcedure

Procedure ExpanderGadget_clicked(*gadget_.expander); if one is clicked..

;process the actual clicked one
Select GetGadgetData(*gadget_\gadget_id)
    Case *gadget_\normal_image
        ;normal state -> switch to expanded state
        _show_gadgets(*gadget_,#True)
    Case *gadget_\expanded_image
        ;expanded state > switch to normal state
        _show_gadgets(*gadget_,#False)
EndSelect 
;close all other expander
ForEach all_expander_gadgets()
    If all_expander_gadgets()\gadget_id<>*gadget_ And all_expander_gadgets()\group=*gadget_\expandergroup
        _show_gadgets(all_expander_gadgets()\gadget_id,#False)      
    EndIf      
Next

;-TODO necessary on windows... on Linux the redraw is done by gtk+ correct
;not the best solution ... I guess.. but fairly working 
; 
CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    r.RECT
    GetClientRect_(WindowID(0),r)
    InvalidateRect_(WindowID(0),r,0)
CompilerEndIf    

While WindowEvent():Wend
ProcedureReturn 1
EndProcedure

Procedure ExpanderGadget(*Gadget_.expander); creates an ExpanderGadget
tmp_img=_create_expander_image(*Gadget_,1)
*Gadget_\Gadget_id=ImageGadget(#PB_Any,*Gadget_\gx,*Gadget_\gy,*Gadget_\gw,*Gadget_\gh,ImageID(tmp_img))
SetGadgetData(*Gadget_\Gadget_id,*gadget_\normal_image)
*Gadget_\container=#PB_Any
FreeImage(tmp_img)
AddElement(all_expander_gadgets())
all_expander_gadgets()\gadget_id=*Gadget_
all_expander_gadgets()\group=*Gadget_\expandergroup
ProcedureReturn *Gadget_\Gadget_id; the Gadgetid for the Expander
EndProcedure

Procedure AddExpanderGadget(*Gadget_.expander,Gadget_number.l,Type.s,x.l,y.l,width.l,height.l,text.s="",color=0); adds a gadget to the Expandergadget
AddElement(expander_gadgets())
expander_gadgets()\Expander_Gadget=*gadget_\gadget_id
expander_gadgets()\Gadget_id=#PB_Any 
expander_gadgets()\gx=x
expander_gadgets()\gy=y
expander_gadgets()\gw=width
expander_gadgets()\gh=height
expander_gadgets()\gadgettext=text
expander_gadgets()\gadgettype=type
expander_gadgets()\nr=Gadget_number
expander_gadgets()\color=color
ProcedureReturn 1
EndProcedure

;--------DEMO------<

UseJPEGImageDecoder()
Define.expander myex_1, myex_2, myex_3,myex_4,myex_5,myex_6,myex_7

OpenWindow(0,0,0,500,330,"ExpanderGadget - Test")
CreateGadgetList(WindowID(0))

mb=ButtonGadget(#PB_Any,400,290,80,30,"exit")
DisableGadget(CheckBoxGadget(#PB_Any,10,180,150,25,"just another gadget"),#False)

myex_1\normal_image=LoadImage(#PB_Any,"blau.jpg")
myex_1\expanded_image=LoadImage(#PB_Any,"blau1.jpg")
myex_1\gx=10
myex_1\gy=10
myex_1\gw=150
myex_1\gh=20
myex_1\fontnr=LoadFont(#PB_Any,"sans",6)
myex_1\gadgettext="options"
myex_1\backgroundcolor=#PB_Any;if you don't want to set an own color, you MUST set it to #pb_any
myex_1\expandergroup=0

myex_2\normal_image=LoadImage(#PB_Any,"blau.jpg")
myex_2\expanded_image=LoadImage(#PB_Any,"blau1.jpg")
myex_2\gx=10
myex_2\gy=70
myex_2\gw=160
myex_2\gh=20
myex_2\fontnr=LoadFont(#PB_Any,"sans",10,#PB_Font_Bold)
myex_2\gadgettext="exit options"
myex_2\backgroundcolor=$8F9EFF;if you don't want to set an own color, you MUST set it to #pb_any
myex_2\expandergroup=0

myex_3\normal_image=LoadImage(#PB_Any,"blau.jpg")
myex_3\expanded_image=LoadImage(#PB_Any,"blau1.jpg")
myex_3\gx=10
myex_3\gy=130
myex_3\gw=160
myex_3\gh=20
myex_3\fontnr=LoadFont(#PB_Any,"courier",9)
myex_3\gadgettext="even more options"
myex_3\backgroundcolor=#PB_Any;if you don't want to set an own color, you MUST set it to #pb_any
myex_3\expandergroup=0

myex_4\normal_image=LoadImage(#PB_Any,"blau.jpg")
myex_4\expanded_image=LoadImage(#PB_Any,"blau1.jpg")
myex_4\gx=230
myex_4\gy=10
myex_4\gw=160
myex_4\gh=20
myex_4\fontnr=LoadFont(#PB_Any,"sans",10)
myex_4\gadgettext="second group 1"
myex_4\backgroundcolor=#PB_Any;if you don't want to set an own color, you MUST set it to #pb_any
myex_4\expandergroup=1

;you can reference to another already loaded image too;
myex_5\normal_image=myex_4\normal_image
myex_5\expanded_image=myex_4\expanded_image
myex_5\gx=230
myex_5\gy=50
myex_5\gw=160
myex_5\gh=20
myex_5\fontnr=LoadFont(#PB_Any,"arial",8)
myex_5\gadgettext="second group 2"
myex_5\backgroundcolor=#PB_Any;if you don't want to set an own color, you MUST set it to #pb_any
myex_5\expandergroup=1

ExpanderSetDefaults(@myex_6)
myex_6\gx=230
myex_6\gy=90
myex_6\gw=160
myex_6\gh=20
myex_6\gadgettext="with built-in images"
myex_6\expandergroup=1


my1=ExpanderGadget(@myex_1)
AddExpanderGadget(@myex_1,1,"optiongadget",10,10,100,20,"testext 1")
AddExpanderGadget(@myex_1,2,"optiongadget",10,30,100,20,"testext 2")
AddExpanderGadget(@myex_1,3,"optiongadget",10,50,100,20,"testext 3")
CloseExpanderGadget(@myex_1)

my2=ExpanderGadget(@myex_2)
AddExpanderGadget(@myex_2,4,"checkboxgadget",10,10,160,20,"enable exit button")
AddExpanderGadget(@myex_2,5,"checkboxgadget",10,30,160,20,"I have no function")
CloseExpanderGadget(@myex_2)

my3=ExpanderGadget(@myex_3)
AddExpanderGadget(@myex_3,6,"textgadget",10,10,170,25,"Please enter your Name")
AddExpanderGadget(@myex_3,7,"stringgadget",10,40,160,25,"")
AddExpanderGadget(@myex_3,8,"hyperlinkgadget",10,80,170,30,"klick me to exit the App",$0309FC)
CloseExpanderGadget(@myex_3)

my4=ExpanderGadget(@myex_4)
wy=10
For m=9 To 12
    AddExpanderGadget(@myex_4,m,"HyperLinkGadget",10,wy,160,30,"entry number "+Str(m-8),RGB(255-wy, 0, 91+wy))
    wy+30
Next  
CloseExpanderGadget(@myex_4)

my5=ExpanderGadget(@myex_5)
wy=10
For m=13 To 17
    AddExpanderGadget(@myex_5,m,"HyperLinkGadget",10,wy,160,30,"entry number "+Str(m-12),RGB(255-wy, 0+wy, 255))
    wy+30
Next  
CloseExpanderGadget(@myex_5)

my6=ExpanderGadget(@myex_6)
wy=10
For m=18 To 23
    AddExpanderGadget(@myex_6,m,"HyperLinkGadget",10,wy,160,30,"entry number "+Str(m-12),RGB(255-wy, 0+wy, 25))
    wy+30
Next  
CloseExpanderGadget(@myex_6)


SetGadgetState(its_Gadget(2),#True)
SetGadgetState(its_Gadget(5),#True)
DisableGadget(mb,#True)

Repeat
event=WaitWindowEvent(0)
Select Event
    Case #PB_Event_Gadget
        Select EventGadget()
            Case my1
                expandergadget_clicked(@myex_1)
            Case my2
                expandergadget_clicked(@myex_2)                                                
            Case my3
                expandergadget_clicked(@myex_3)        
            Case my4
                expandergadget_clicked(@myex_4)         
            Case my5
                expandergadget_clicked(@myex_5)       
            Case my6
                expandergadget_clicked(@myex_6)       
            Case its_gadget(4)
                Select GetGadgetState(its_gadget(4))
                    Case 1
                        DisableGadget(mb,#False)
                    Case 0
                        DisableGadget(mb,#True)
                EndSelect   
            Case its_gadget(8)
                quit=1
            Case mb
                quit=1
        EndSelect   
    Case #PB_Event_CloseWindow
        quit=1
EndSelect       

Until quit=1
End
You'll need these two images...:

Image

Image
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Post by rsts »

Another nice one. I'm sure it will spur more interest.

Keep them coming :D

btw - seems to work fine here even w/mouse over checkbox. Maybe I misunderstood.

cheers

vista 64 dual core amd
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

:D

if you open the last expander ("even more options") on the left side (the one with the input field) the checkbox ("just another gadget") is covered by the expander... if you now move the mouse over the expander and the coordinates of the checkbox are reached... it will pop up (if this doesn't happen... it's maybe an issue with XP)

EDIT:

Maybe this http://www.purebasic.fr/english/viewtopic.php?t=31332 solved that problem already.... (we'll see in the next version)
CSAUER
Enthusiast
Enthusiast
Posts: 188
Joined: Mon Oct 18, 2004 7:23 am
Location: Germany

Post by CSAUER »

This is a good idea.
We have got always the problem, that the event manager is working in a different direction than drawing the gadgets, if they are averlaying.
I requested this already, but Freak said, that there will be no support for overlaying gadgets. I solved this problem by programming a complete GUI system which covers all gadgets and disables catching events, when it is hidden by another gadget.

I think this kind of expanders should not work like a combo. They should move all content below like a property bar. That means, all gadgets below the expander should move down, if the expander is expanded and they should move back to the original position when it is collaped. Therefore I think you should need even a own GUI system.
I did this already within one application manually.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PB4.1 - Win: MacBook black 2008 2,4 GHz, 4 GB RAM, MacOSX 10.5/VMWare/WinXP
PB4.1 - Mac: MacMini G4 1,4 GHz, 512 MB RAM, MacOSX 10.4
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

I know, that the "standard" behavior of an expander is like you'd described it.. it would be easy if there is a function to Enumerate all used gadgets in a window and get info's about position and size... (there is an existing feature request for a function like that - hope it will be implemented someday :cool:)
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Post by superadnim »

Very nice indeed, however I do get rendering issues with the "just another gadget" checkbox and the "even more options" one.

Also, they never redraw if you change the focus of the window! (ie hide it and show it again, nothing will be drawn until you send a few events)

:lol: should I bash the keyboard and give up?
:?
Post Reply