A lot of TreeView Stuff :-)

Share your advanced PureBasic knowledge/code with the community.
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

A lot of TreeView Stuff :-)

Post by BackupUser »

Code updated For 5.20+


Restored from previous forum. Originally posted by freak.

Hi all,

I've been playing around with the TreeViewGadget, and found, that
there was a lot of stuff missing.
I also couldn't get SetGadgetItemText() and GetGadgetItemText() to
work with the TreeView.
So I had to write my own procedures.

Here they are, some more are going to follow, if i have some more time.

Use them as you want, replys and suggestions are welcome.

Code: Select all

; These Structures are needed for: TVAddItem(), TVGetItemName() and TVSetItemName()
; *****************************************
;

;
Structure TVINSERTSTRUCT
  hParent.l
  hInsertAfter.l
  item.TVITEM
EndStructure
;
;
;
;
Procedure TVAddItem(gadget.l, position.l, text.s, hImg.l, openflag.l)
  ;
  ; Insert a Item in a TreeView Gadget.
  ; not like AddGadgetItem(), this one supports the position parameter.
  ;
  ; Usage:
  ;***********
  ; gadget.l   = PB Gadget Number
  ; position.l = Item to insert the new one after (starting with 0)
  ; text.s     = Item Text
  ; hImg.l     = ImageID if Image to display
  ; openflag.l = If #TRUE, a new TreeViewNode is created at 'position.l' and the new Item
  ;              is added as it's Child, if #FALSE, the new one is just inserted after the 'position.l'
  ;              Item.
  ;
  ; Note: The hImg.l parameter is only supported, if there are allready some Items with Images.
  ;
  hwndTV.l = GadgetID(gadget)
  hRoot.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  hItem = hRoot: hParent.l = 0
  For i.l = 0 To position-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  lpis.TVINSERTSTRUCT
  If openflag = #True
    pitem.TVITEM
    pitem\mask = #TVIF_CHILDREN | #TVIF_HANDLE
    pitem\hItem = hItem
    pitem\cChildren = 1
    SendMessage_(hwndTV, #TVM_SETITEM, 0, @pitem)
    lpis\hParent = hItem
    lpis\hInsertAfter = hItem
  Else
    lpis\hParent = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem)
    lpis\hInsertAfter = hItem
  EndIf
  lpis\item\mask = #TVIF_TEXT
  If hImg <> 0
    himl.l = SendMessage_(hwndTV, #TVM_GETIMAGELIST, #TVSIL_NORMAL ,0)
    If himl <> #Null
      lpis\item\mask | #TVIF_IMAGE
      iImage.l = ImageList_AddIcon_(himl, hImg)
      lpis\item\iImage = iImage
      lpis\item\iSelectedImage = iImage
    EndIf
  EndIf
  lpis\item\cchTextMax = Len(text)
  lpis\item\pszText = @text
  SendMessage_(hwndTV, #TVM_INSERTITEM, 0, @lpis)
EndProcedure
;
;
;
;
Procedure TVDeleteItem(gadget.l, item.l)
  ;
  ; Deletes a TreeViewItem.
  ;
  ;Usage:
  ;**************
  ; gadget.l  = PB Gadget Number
  ; item.l    = Item to delete (starting with 0)
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  SendMessage_(hwndTV, #TVM_DELETEITEM, 0, hItem)   
EndProcedure
;
;
;
;
Procedure TVShowItem(gadget.l, item.l)
  ;
  ; Makes sure, an Item is visible. If necessary, the List is expanded and scrolled, so
  ; the User can see the Item
  ;
  ;Usage:
  ;************
  ; gadget.l  = PB Gadget Number
  ; item.l    = Item to make visible.
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  SendMessage_(hwndTV, #TVM_ENSUREVISIBLE, 0, hItem)
EndProcedure
;
;
;
;
;
Procedure.s TVGetItemName(gadget.l, item.l)
  ;
  ; Get the Name of a TreeViewItem. (I couldn't get GetGadgetItemText() to work, so i use this one)
  ;
  ;Usage:
  ;*************
  ; gadget.l = PB Gadget Number
  ; item.l   = Item to get the Text of (starting with 0)
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  text.s = Space(999)
  pitem.TVITEM
  pitem\mask = #TVIF_TEXT
  pitem\hItem = hItem
  pitem\pszText = @text
  pitem\cchTextMax = 999
  SendMessage_(hwndTV, #TVM_GETITEM, 0, @pitem)
  ProcedureReturn PeekS(pitem\pszText)
EndProcedure
;
;
;
;
Procedure TVSetItemName(gadget.l, item.l, text.s)
  ;
  ; Set the Text of a TreeViewItem. (I couldn't get SetGadgetItemText() to work, so i use this one)
  ;
  ;Usage:
  ;*************
  ; gadget.l = PB Gadget Number
  ; item.l   = Item to set the Text of (starting with 0)
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  pitem.TVITEM
  pitem\mask = #TVIF_TEXT
  pitem\hItem = hItem
  pitem\pszText = @text
  pitem\cchTextMax = Len(text)
  SendMessage_(hwndTV, #TVM_SETITEM, 0, @pitem)
EndProcedure
;
;
;
;
Procedure TVExpandNode(gadget.l, item.l, flag.l)
  ;
  ; Expands, or collapses a TreeViewNode.
  ;
  ;Usage:
  ;*************
  ; gadget.l  = PB Gadget Number
  ; item.l    = Item to expand/collapse
  ; flag.l    = If 0: the Node collapses
  ;             If 1: the Node is expanded
  ;             If 2: the Node is expanded, if it was collapsed, and it's collapsed, if it was expanded
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  If flag = 1
    SendMessage_(hwndTV, #TVM_EXPAND, #TVE_EXPAND, hItem)
  ElseIf flag=2
    SendMessage_(hwndTV, #TVM_EXPAND, #TVE_TOGGLE, hItem)
  Else
    SendMessage_(hwndTV, #TVM_EXPAND, #TVE_COLLAPSE, hItem)
  EndIf
EndProcedure
;
;
;
;
Procedure TVExpandAll(gadget.l)
  ;
  ; Expands the whole TreeView, good for using, after it was created, to show the whole tree.
  ;
  ; Usage:
  ;************
  ; gadget.l = PB GAdget Number
  ;
  hwndTV.l = GadgetID(gadget)
  hRoot.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  hItem.l = hRoot
  Repeat
    SendMessage_(hwndTV, #TVM_EXPAND, #TVE_EXPAND, hItem)
    hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXTVISIBLE , hItem)
  Until hItem = #Null
  SendMessage_(hwndTV, #TVM_ENSUREVISIBLE, 0, hRoot)
EndProcedure
;
;
;
;
Procedure TVSortNode(gadget.l, item.l, flag.l)
  ;
  ; Sorts all child Items of a TreeViewNode.
  ;
  ;Usage:
  ;***********
  ; gadget.l  = PB Gadget Number
  ; item.l    = Item where the Node Starts (the one with the '+')
  ; flag.l    = If #TRUE, all SubNodes are Sorted, too.
  ;           = If #FALSE, only the direct child Items of this Node are sorted.
  ;
  hwndTV.l = GadgetID(gadget)
  hItem.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To item-1
    hItem2.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #Null: hItem2 = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #Null: hItem = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2 <> #Null
    hItem = hItem2
  Next i
  SendMessage_(hwndTV, #TVM_SORTCHILDREN, flag, hItem)
EndProcedure
;
;
;
;
Procedure TVSortAll(gadget.l)
  ;
  ; Sorts the whole TreeView. This can also be done by 'TVSortNode(gadget.l, 0, #TRUE)', but
  ; this one is much less code :)
  ;
  ;Usage:
  ;***********;
  ; gadget.l  = PB Gadget Number
  ;
  hwndTV.l = GadgetID(gadget)
  hRoot.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  SendMessage_(hwndTV, #TVM_SORTCHILDREN, #True, hRoot)
EndProcedure



--------------------------------
Programming today is a race between software engineers striving to build bigger and
better idiot-proof programs and the universe trying to produce bigger and better idiots.

...So far, the universe is winning.
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Fangbeast.

Just a quick note. I don't know if you intended to declare some structures twice or you were just commenting the fact for us beginners (grin) but structures are global so there is no need to define in a procedure and the main body of the program. You get a "Structure already defined" error. I apologise if I misread your intent :)

Fangles
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by freak.

Ooops, i first used it only in this procedure, and then i needed it in others, too, and i forgot to delete it there (didn't test after
that)

Thanks for the hint... it's changed now.

Timo
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Fangbeast.

Your code solved a long standing problem for me. I knew you could insert an item but not how so I was always having to rebuild and redraw the entire tree after an addition was made to it and that caused severe flickering and I had to find another way. Now I can go back and do it the way I wanted to.

Being able to insert AND delete items anywhere simplifies certain bits of code I am using enormously and gives rise to some great ideas.

Thanks heaps :):)

Fangles
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by altesocke.

thank you, your procedures are very helpful for my current project.

if you want to have icons in your TreeView (open folder if selected and closed folder if unselected for example) you need a image list associated to your TreeView.

to include the icons in your exe and use it in the image list, you can use the following commands:

Code: Select all

hndoopen = CatchImage(0, ?oopen)   ; catch the included icons (only *.bmp are supported for now)
hndoclosed = CatchImage(1, ?oclosed)
InitCommonControls_()	
hndImgList = ImageList_Create_(16, 16, #ILC_COLOR16, 2, 4) ; create a image list the two first params specify height and width of your icons
ImageList_Add_(hndImgList, hndoopen, 0) ; add the first icon to the image list
ImageList_Add_(hndImgList, hndoclosed, 0) ; add the second icon
SendMessage_(hwndTV, #TVM_SETIMAGELIST, #TVSIL_NORMAL, hndImgList) ; associate the image list with your treeview

; at the end of your code:
oopen: IncludeBinary "oopen.bmp"      ;  include your icons in the exe
oclosed: IncludeBinary "oclosed.bmp"

when an item should be inserted in your TreeView, you should modify the above TVAddItem() procedure a little bit (remove or ignore the hImg parameter) and insert the icons like this:

Code: Select all

himl.l = SendMessage_(hwndTV, #TVM_GETIMAGELIST, #TVSIL_NORMAL ,0) ; get the handle to the treeview image list
If himl  #NULL
 lpis\item\mask | #TVIF_IMAGE
 lpis\item\iImage = 1  ; second icon from the image list for "not selected"
 lpis\item\iSelectedImage = 0 ; first icon from the image list for selected state
endif 
for mor infos about using image lists: http://msdn.microsoft.com/library/en-us ... eflist.asp or the platform sdk.

cu socke
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Justin.

This is very cool, i was able to include icons inside the executable and use them in a listview, it should work with all the gadgets, thanks :)
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by ricardo.

Great stuff!!

But in my code all the added items (by this way) get 0 in GetGadgetState(), then i can't figure if the user click on the created items in which one does he clicks or selects.
I try to use
Num.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CARET, #NULL)
But dosent work

Any idea?


Best Regards

Ricardo

Dont cry for me Argentina...
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by freak.

> But in my code all the added items (by this way) get 0 in GetGadgetState(),

Ooops, I didn't realize that, i actually did all event handling myself.

> Num.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CARET, #NULL)

The problem here is, that the returned Value is a Handle, not the Item's Number. In TreeGadget it's always Handles, not Numbers.

I wrote two procedures for that:

GetItemID() - Returns the Handle to an ItemNumber, you can use it, if you want to send Messages to a specific Item using API.

GetItemNumber() - Returns the ItemNumber for a specific Handle. Use this, if you got a handle as result, and want to know the Number.

In your case it would look like this:

Code: Select all

handle.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CARET, #NULL)
Num.l = GetItemNumber(#Gadget, handle)
Note, #Gadget is not the Windows Handle, but the PB Gadget ID here. if
you want to change that, replace 'GadgetID(Gadget)' with 'Gadget' the Procedures.

So here they are...

Code: Select all

Procedure.l GetItemID(Gadget.l, Item.l)
  Protected hItem.l, hItem2.l
  hItem = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  For i.l = 0 To Item-1
    hItem2 = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #NULL: hItem2 = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #NULL: hItem = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2  #NULL
    hItem = hItem2
  Next i
  ProcedureReturn hItem
EndProcedure

  

Procedure.l GetItemNumber(Gadget.l, hItem.l)
  Protected hItem1.l, hItem2.l, ItemNum.l
  hItem1 = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_ROOT, 0)
  While hItem1  hItem
    hItem2 = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_CHILD, hItem)
    Repeat
      If hItem2 = #NULL: hItem2 = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_NEXT, hItem): EndIf
      If hItem2 = #NULL: hItem = SendMessage_(GadgetID(Gadget), #TVM_GETNEXTITEM, #TVGN_PARENT, hItem): EndIf
    Until hItem2  #NULL
    hItem = hItem2
    ItemNum + 1
  Wend
  ProcedureReturn ItemNum
EndProcedure
That's it, hope it helps...

Timo
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by ricardo.


Thanks a lot !!!

Im sure that in a near future Fred will implement all the gadgets with more complete posibilities. If -as he said- his keyword is KEEP IT EASY, this limitations and workarounds on the gadgets broke the 'EASY' rule.
I know its possible to read about APIs and find a way, but i think that if the goal is to keep it simple and easy, this things just dont help to acomplish it.:)

Freak, really thanks : )


Best Regards

Ricardo

Dont cry for me Argentina...
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by ricardo.

@Freak

Im trying to create and delete items on runtime. The idea is that the user select some Item and can add items or subitems into the selected position or delete the selected item or subitem.

Everything goes fine until they select one item that has some node before.
Imagine there are 4 Items

-Item1
-Item2
-Item3
-Item4

Then if i select Item 3 and add a subitem i get a node in subitem 3 with a subitem. Perfect.
But if i try the to do the same with item4, add a subitem... the item was created as a subitem of Item3!! No way to acomplish it if any items has subitem.

I dont post my code since it has many other things and could be difficult to read since its very big.

Im detecting which item is selected using:

handle.l = SendMessage_(GadgetID(#Tree), #TVM_GETNEXTITEM, #TVGN_CARET, #NULL)

and then get the number of the item selected. .. all works fine. The problem is only when trying to add a subitem IF any other Item before has a node and subitems.

Hope can help me!!

Best Regards

Ricardo

Dont cry for me Argentina...
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by freak.

There seemed to be a little bug in there that caused that.
I changed it in my sources, but it seems like I forgot to do that here, sorry.

I changed that now, and the Code should work fine.
Just cut&paste the above Code again into your project.
Can you please tell me, if there are any other problems?

Timo
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by ricardo.

Thats what a call a FASTTT answer!!!

Really thanks now its working fine!!!!!



Best Regards

Ricardo

Dont cry for me Argentina...
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Eikeland.

Hi,
Thanks for the functions, they are great! :)
....but somthing like this should absolute been included from PB.

Anway... my application freeze and the CPU goes up to 100% when I try to use the GetItemID and GetItemNumber, I'm using PB 3.61...any idea what's wrong.

Thanks
Richard
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by freak.

Did it work with previous versions of PB?

Maybe there is something wrong in you code. Make sure the Gadget Number is correct, and the Item number exists in you Gadget. This function doesn't provide any checks, so if you specify an item that is higher tnat the total number of items, the Function will search forever.

So be careful with that.

Timo
BackupUser
PureBasic Guru
PureBasic Guru
Posts: 16777133
Joined: Tue Apr 22, 2003 7:42 pm

Post by BackupUser »

Restored from previous forum. Originally posted by Eikeland.

Hi,
Thanks for your responce.

This is what I use under the click event

handle.l = SendMessage_(hwndTV, #TVM_GETNEXTITEM, #TVGN_CARET, #NULL)
Num.l = GetItemNumber(#Gd_TV, handle)
Debug num

I have of course duble checked the gadget ID and the TV Handle.
I'm using Windows XP, and PB 3.61, but I did never test it on 3.51.

Anyway, please do not make a big deal about this, I have a workaround for now.

Again Thanks for a nice code

Richard
Post Reply