First of all (as I said on irc) - thanks for sharing such wonderful code. Today
I found the time to play around with it ...
Unfortunately I have some problems with setting the callback a second time.
.
Complete code (PB4.11Beta). Any help/hint is highly appreciated 
Code: Select all
;- IID Datasection
DataSection
  IID_IHTMLElement: ; {3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}
    Data.l $3050F1FF
    Data.w $98B5, $11CF
    Data.b $BB, $82, $00, $AA, $00, $BD, $CE, $0B        
  
  IID_IHTMLDocument2: ; {332C4425-26CB-11D0-B483-00C04FD90119}
    Data.l $332C4425
    Data.w $26CB, $11D0
    Data.b $B4, $83, $00, $C0, $4F, $D9, $01, $19        
    
  IID_NULL: ; {00000000-0000-0000-0000-000000000000}
    Data.l $00000000
    Data.w $0000, $0000
    Data.b $00, $00, $00, $00, $00, $00, $00, $00 
    
  IID_IUnknown: ; {00000000-0000-0000-C000-000000000046}
    Data.l $00000000
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  IID_IDispatch: ; {00020400-0000-0000-C000-000000000046}
    Data.l $00020400
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46
EndDataSection
;- IDispatch Implementation
Structure IDispatchVtbl
  QueryInterface.l
  AddRef.l
  Release.l
  GetTypeInfoCount.l
  GetTypeInfo.l
  GetIDsOfNames.l
  Invoke.l
EndStructure
Structure IDispatchObject
  Vtbl.l    
  RefCount.l
  
  Function.l
  Gadget.l
  Window.IHTMLWindow2
  IsMouseHandler.l
EndStructure
Global NewList IDispatchObjects.IDispatchObject()
Procedure IDispatch_QueryInterface(*THIS.IDispatchObject, *IID.IID, *Object.LONG)
  If *Object = 0
    ProcedureReturn #E_INVALIDARG
  ElseIf CompareMemory(*IID, ?IID_IUnknown, SizeOf(IID)) Or CompareMemory(*IID, ?IID_IDispatch, SizeOf(IID))
    *Object\l = *THIS
    *THIS\RefCount + 1
    ProcedureReturn #S_OK  
  Else
    *Object\l = 0
    ProcedureReturn #E_NOINTERFACE  
  EndIf
EndProcedure
Procedure IDispatch_AddRef(*THIS.IDispatchObject)
  *THIS\RefCount + 1
  ProcedureReturn *THIS\RefCount
EndProcedure
Procedure IDispatch_Release(*THIS.IDispatchObject)
  *THIS\RefCount - 1
  If *THIS\RefCount <= 0    
    ChangeCurrentElement(IDispatchObjects(), *THIS)    
    IDispatchObjects()\Window\Release()    
    DeleteElement(IDispatchObjects())
    ProcedureReturn 0
  Else
    ProcedureReturn *THIS\RefCount
  EndIf
EndProcedure
Procedure IDispatch_GetTypeInfoCount(*THIS.IDispatchObject, *pctinfo.LONG)
  If *pctinfo = 0
    ProcedureReturn #E_INVALIDARG
  Else
    *pctinfo\l = 0
    ProcedureReturn #S_OK
  EndIf
EndProcedure
Procedure IDispatch_GetTypeInfo(*THIS.IDispatchObject, iTInfo, lcid, *pptInfo)
  ProcedureReturn #E_NOTIMPL
EndProcedure
Procedure IDispatch_GetIDsOfNames(*THIS.IDispatchObject, *riid.IID, *rgszNames, cNames, lcid, *rgDispID.LONG)
  If CompareMemory(*riid, ?IID_NULL, SizeOf(IID)) = 0
    ProcedureReturn #DISP_E_UNKNOWNINTERFACE
  ElseIf *rgDispID = 0 Or cNames = 0
    ProcedureReturn #E_INVALIDARG
  Else
    While cNames > 0 ; we provide no names, so set all passed fields to DISPID_UNKNOWN (-1)
      *rgDispID\l = -1
      *rgDispID + 4
      cNames - 1
    Wend
    ProcedureReturn #DISP_E_UNKNOWNNAME
  EndIf
EndProcedure
Procedure IDispatch_Invoke(*THIS.IDispatchObject, dispIdMember, *riid.IID, lcid, wFlags.w, *pDispParams.DISPPARAMS, *pVarResult.VARIANT, *pExcpInfo, *puArgErr)
  If CompareMemory(*riid, ?IID_NULL, SizeOf(IID)) = 0
    ProcedureReturn #DISP_E_UNKNOWNINTERFACE
  ElseIf dispIdMember <> 0 Or wFlags <> #DISPATCH_METHOD
    ProcedureReturn #DISP_E_MEMBERNOTFOUND
  ElseIf *pDispParams = 0
    ProcedureReturn #E_INVALIDARG
  ElseIf *pDispParams\cNamedArgs > 0
    ProcedureReturn #DISP_E_NONAMEDARGS
  ElseIf *pDispParams\cArgs > 0
    ProcedureReturn #DISP_E_BADPARAMCOUNT
  Else
    
    If *THIS\Window\get_event(@Event.IHTMLEventObj) = #S_OK
      If *THIS\IsMouseHandler
      
        If Event\get_button(@button) = #S_OK
          If button = 2 Or button = 3 Or button = 6 Or button = 7
            varReturn.VARIANT\vt = #VT_BOOL
            varReturn\boolVal    = #VARIANT_FALSE        
            Event\put_returnValue(varReturn)
          EndIf
        EndIf
      
      Else
    
        If Event\get_srcElement(@Element.IHTMLElement) = #S_OK
        
          ; Walk up the tags until the actual link is found, as there can be
          ; an image inside the link for example
          ;
          Repeat
            Abort = 1
            If Element\get_tagName(@bstr_tag) = #S_OK And bstr_tag
              Tag$ = PeekS(bstr_tag, -1, #PB_Unicode)
              SysFreeString_(bstr_tag)
              
              If UCase(Tag$) <> "A"
                If Element\get_parentElement(@Parent.IHTMLElement) = #S_OK
                  Element\Release()
                  Element = Parent
                  Abort = 0
                EndIf
              EndIf
            EndIf
          Until Abort
        
          If Element\get_className(@bstr_class) = #S_OK And bstr_class
            Class$ = PeekS(bstr_class, -1, #PB_Unicode)
            SysFreeString_(bstr_class)
          EndIf
          
          If Element\get_id(@bstr_id) = #S_OK And bstr_id
            ID$ = PeekS(bstr_id, -1, #PB_Unicode)
            SysFreeString_(bstr_id)        
          EndIf
          
          If Element\get_innerText(@bstr_text) = #S_OK And bstr_text
            Text$ = PeekS(bstr_text, -1, #PB_Unicode)
            SysFreeString_(bstr_text)        
          EndIf
                    
          If Element\getAttribute("href", 0, @varResult.VARIANT) = #S_OK
            If varResult\vt = #VT_BSTR And varResult\bstrVal
              Link$ = PeekS(varResult\bstrVal, -1, #PB_Unicode)
              SysFreeString_(varResult\bstrVal)             
            EndIf
          EndIf
          
          If CallFunctionFast(*THIS\Function, *THIS\Gadget, Link$, Text$, ID$, Class$)
            varReturn.VARIANT\vt = #VT_BOOL
            varReturn\boolVal    = #VARIANT_TRUE        
          Else
            varReturn.VARIANT\vt = #VT_BOOL
            varReturn\boolVal     = #VARIANT_FALSE        
          EndIf        
          Event\put_returnValue(varReturn)
        
        EndIf
      EndIf      
      Event\Release()
    EndIf 
    ProcedureReturn #S_OK
  EndIf
    
EndProcedure
Global IDispatchVtbl.IDispatchVtbl
IDispatchVtbl\QueryInterface   = @IDispatch_QueryInterface()
IDispatchVtbl\AddRef           = @IDispatch_AddRef()
IDispatchVtbl\Release          = @IDispatch_Release()
IDispatchVtbl\GetTypeInfoCount = @IDispatch_GetTypeInfoCount()
IDispatchVtbl\GetTypeInfo      = @IDispatch_GetTypeInfo()
IDispatchVtbl\GetIDsOfNames    = @IDispatch_GetIDsOfNames()
IDispatchVtbl\Invoke           = @IDispatch_Invoke()
;- Webgadget Functions
Procedure WebGadget_Open(Gadget, AddHistory)
  Shared WebGadget_Document.IHTMLDocument2
  result = 0
  
  If GetGadgetText(Gadget) = ""
    SetGadgetText(Gadget, "about:blank")
  EndIf
  
  WebGadget_Document = 0
  Browser.IWebBrowser2 = GetWindowLong_(GadgetID(Gadget), #GWL_USERDATA)
  If Browser
    If Browser\get_Document(@DocumentDispatch.IDispatch) = #S_OK
      If DocumentDispatch\QueryInterface(?IID_IHTMLDocument2, @WebGadget_Document.IHTMLDocument2) = #S_OK      
;         varReplace.VARIANT\vt = #VT_BOOL
;         If AddHistory                    
;           varReplace\boolVal = #VARIANT_FALSE
;         Else
;           varReplace\boolVal = #VARIANT_TRUE
;         EndIf
; 
;         varName.VARIANT\vt = #VT_BSTR
;         Unicode$ = Space(16)
;         PokeS(@Unicode$, "replace", -1, #PB_Unicode)
;         varName\bstrVal = SysAllocString_(@Unicode$)               
;         varFeatures.VARIANT\vt = #VT_BSTR
;         varFeatures\bstrVal    = SysAllocString_(@NULLString.l) 
;         
;         If WebGadget_Document\open("text/html", varName, varFeatures, varReplace, @Dummy.IDispatch) = #S_OK
;           Dummy\Release()
;           result = 1
;         EndIf       
;          
;         SysFreeString_(varName\bstrVal)  
;         SysFreeString_(varFeatures\bstrVal)             
        
        varName.VARIANT\vt = #VT_BSTR
        If AddHistory                    
          varName\bstrVal = SysAllocString_(@NULLString.l)
        Else
          Unicode$ = Space(16)
          PokeS(@Unicode$, "replace", -1, #PB_Unicode)
          varName\bstrVal = SysAllocString_(@Unicode$) 
        EndIf
        
        varEmpty.VARIANT\vt = #VT_EMPTY
        
        If WebGadget_Document\open("text/html", varName, varEmpty, varEmpty, @Dummy.IDispatch) = #S_OK
          If Dummy
            Dummy\Release()
          EndIf
          
          result = 1
        EndIf        
        
        SysFreeString_(varName\bstrVal)               
        
        
      EndIf          
      DocumentDispatch\Release()
    EndIf  
  EndIf
  
  ProcedureReturn result
EndProcedure
Procedure WebGadget_Write(String$)
  Shared WebGadget_Document.IHTMLDocument2
  
  If WebGadget_Document
    Unicode$ = Space(Len(String$)*2+2)
    PokeS(@Unicode$, String$, -1, #PB_Unicode)
    bstr_string = SysAllocString_(@Unicode$)   
  
    *sfArray = SafeArrayCreateVector_(#VT_VARIANT, 0, 1)
    If *sfArray      
      If SafeArrayAccessData_(*sfArray, @*varParam.VARIANT) = #S_OK
      
        *varParam\vt = #VT_BSTR
        *varParam\bstrVal = bstr_string
        
        If SafeArrayUnaccessData_(*sfArray) = #S_OK
          WebGadget_Document\write(*sfArray)
        EndIf
      
      EndIf      
      SafeArrayDestroy_(*sfArray)
    EndIf
    
    SysFreeString_(bstr_string)
  EndIf  
EndProcedure
Procedure WebGadget_Close()
  Shared WebGadget_Document.IHTMLDocument2
  If WebGadget_Document
    WebGadget_Document\close()
    WebGadget_Document\Release()
    WebGadget_Document = 0
  EndIf
EndProcedure
Procedure WebGadget_IsLoaded(Gadget)
  Browser.IWebBrowser2 = GetWindowLong_(GadgetID(Gadget), #GWL_USERDATA)
  If Browser
    If Browser\get_ReadyState(@state) = #S_OK
      If state = 4
        ProcedureReturn 1
      EndIf
    EndIf
  EndIf
  
  ProcedureReturn 0
EndProcedure
Procedure WebGadget_CatchLinks(Gadget, Callback)
  result = 0  
  
  If GetGadgetText(Gadget) = ""
    SetGadgetText(Gadget, "about:blank")
  EndIf  
  Browser.IWebBrowser2 = GetWindowLong_(GadgetID(Gadget), #GWL_USERDATA)
  If Browser
    If Browser\get_Document(@DocumentDispatch.IDispatch) = #S_OK
      If DocumentDispatch
        If DocumentDispatch\QueryInterface(?IID_IHTMLDocument2, @Document.IHTMLDocument2) = #S_OK      
        
          If Document\get_links(@LinkCollection.IHTMLElementCollection) = #S_OK        
            If LinkCollection\get_length(@LinkCount) = #S_OK
            
              If LinkCount = 0
                result = 1
                
              Else
                If Document\get_parentWindow(@Window.IHTMLWindow2) = #S_OK
                  
                  AddElement(IDispatchObjects())
                  IDispatchObjects()\Vtbl     = @IDispatchVtbl
                  IDispatchObjects()\RefCount = 1
                  IDispatchObjects()\Window   = Window
                  IDispatchObjects()\Gadget   = Gadget
                  IDispatchObjects()\Function = Callback
                  IDispatchObjects()\IsMouseHandler = 0
                  Dispatch.IDispatch = @IDispatchObjects()
                  
                  varDispatch.VARIANT
                  varDispatch\vt = #VT_DISPATCH
                  varDispatch\pdispVal = Dispatch                    
                  
                  AddElement(IDispatchObjects())
                  IDispatchObjects()\Vtbl     = @IDispatchVtbl
                  IDispatchObjects()\RefCount = 1
                  IDispatchObjects()\Window   = Window
                  IDispatchObjects()\Gadget   = Gadget
                  IDispatchObjects()\Function = Callback
                  IDispatchObjects()\IsMouseHandler = 1
                  MouseDispatch.IDispatch = @IDispatchObjects()                  
                  
                  varDispatch2.VARIANT
                  varDispatch2\vt = #VT_DISPATCH
                  varDispatch2\pdispVal = MouseDispatch                
                  
                  For index = 0 To LinkCount-1
                    varIndex.VARIANT\vt = #VT_I4
                    varIndex\lVal = index      
                    ElementDispatch.IDispatch = 0
                    
                    If LinkCollection\item(varIndex, varIndex, @ElementDispatch.IDispatch) = #S_OK
                      If ElementDispatch ; must check this value according to the docs, as even on failure, #S_OK is returned
               
                        If ElementDispatch\QueryInterface(?IID_IHTMLElement, @Element.IHTMLElement) = #S_OK              
                        
                          Element\put_onclick(varDispatch) 
                          Element\put_onmouseup(varDispatch2) 
                          Element\put_onmousedown(varDispatch2) 
                          Element\Release()
                          
                        EndIf          
                  
                        ElementDispatch\Release()
                      EndIf
                    EndIf                  
                  Next index
                  
                  MouseDispatch\Release()
                  If Dispatch\Release() <> 0
                    result = 1
                  EndIf
                  
                EndIf                           
              EndIf          
            
            EndIf                  
            LinkCollection\Release()
          EndIf
                      
          Document\Release()
        EndIf          
        DocumentDispatch\Release()
      EndIf
    EndIf  
  EndIf
  ProcedureReturn result
EndProcedure
#WebGadget = 0
#WebGadget2= 1
Procedure LinkCallback(Gadget, Link$, Text$, ID$, Class$)
  Debug "Link: "+Link$
  Debug "Text: "+Text$
  Debug ""
  ProcedureReturn 0 ; prevent link from executing
EndProcedure
If OpenWindow(0, 0, 0, 800, 600, "Test", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  If CreateGadgetList(WindowID(0))
   
    WebGadget(#WebGadget, 0, 0, 800, 600, "")   
           
    If WebGadget_Open(#WebGadget, 0)        
   
      WebGadget_Write("<html><body><center><br>")
      WebGadget_Write("<a href="+Chr(34)+"http://www.purebasic.com"+Chr(34)+">PureBasic.com</a><br>")
      WebGadget_Write("<a href="+Chr(34)+"http://forums.purebasic.com"+Chr(34)+">Forum</a><br>")     
      WebGadget_Write("</center></body></html>")
   
      WebGadget_Close()
    EndIf
   
    WebGadget_CatchLinks(#WebGadget, @LinkCallback())           
    Repeat
    Until WaitWindowEvent() = #PB_Event_CloseWindow      
  
    ;If you comment out the following 2 lines - it is the other way around
    ;FreeGadget(#WebGadget)
    ;WebGadget(#WebGadget, 0, 0, 800, 600, "")   
    
    If WebGadget_Open(#WebGadget, 0)
   
      WebGadget_Write("<html><body><center><br>")
      WebGadget_Write("<a href="+Chr(34)+"http://www.purebasic.com"+Chr(34)+">PureBasic.com2</a><br>")
      WebGadget_Write("<a href="+Chr(34)+"http://forums.purebasic.com"+Chr(34)+">Forum2</a><br>")       
      WebGadget_Write("</center></body></html>")
   
      WebGadget_Close()
    EndIf
   
    WebGadget_CatchLinks(#WebGadget, @LinkCallback())    
      
    Repeat
    Until WaitWindowEvent() = #PB_Event_CloseWindow   
   
  EndIf
EndIf