# -*- coding: latin-1 -*-

# PyWin32 Internet Explorer Toolbar
#
# written by Leonard Ritter (paniq@gmx.net)
# and Robert Förtsch (info@robert-foertsch.com)


"""
This sample implements a simple IE Toolbar COM server
supporting Windows XP styles and access to
the IWebBrowser2 interface.

It also demonstrates how to hijack the parent window
to catch WM_COMMAND messages.
"""

# imports section
import sys, os
from win32com import universal
from win32com.client import gencache, DispatchWithEvents, Dispatch
from win32com.client import constants, getevents
import win32com
import pythoncom
import _winreg

from win32com.shell import shell
from win32com.shell.shellcon import *
from win32com.axcontrol import axcontrol

try:
    # try to get styles (winxp)
    import winxpgui as win32gui
except:
    # import default module (win2k and lower)
    import win32gui
import win32ui
import win32con
import commctrl

import array, struct

# ensure we know the ms internet controls typelib so we have access to IWebBrowser2 later on
win32com.client.gencache.EnsureModule('{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}',0,1,1)

# 
IDeskBand_methods = ['GetBandInfo']
IDockingWindow_methods = ['ShowDW','CloseDW','ResizeBorderDW']
IOleWindow_methods = ['GetWindow','ContextSensitiveHelp']
IInputObject_methods = ['UIActivateIO','HasFocusIO','TranslateAcceleratorIO']
IObjectWithSite_methods = ['SetSite','GetSite']
IPersistStream_methods = ['GetClassID','IsDirty','Load','Save','GetSizeMax']

_ietoolbar_methods_ = IDeskBand_methods + IDockingWindow_methods + \
                      IOleWindow_methods + IInputObject_methods + \
                      IObjectWithSite_methods + IPersistStream_methods
_ietoolbar_com_interfaces_ = [
    shell.IID_IDeskBand, # IDeskBand
    axcontrol.IID_IObjectWithSite, # IObjectWithSite
    pythoncom.IID_IPersistStream,
    axcontrol.IID_IOleCommandTarget,
]

class WIN32STRUCT:
    def __init__(self, **kw):
        full_fmt = ""
        for name, fmt, default in self._struct_items_:
            self.__dict__[name] = None
            if fmt == "z":
                full_fmt += "pi"
            else:
                full_fmt += fmt
        for name, val in kw.iteritems():
            self.__dict__[name] = val

    def __setattr__(self, attr, val):
        if not attr.startswith("_") and attr not in self.__dict__:
            raise AttributeError(attr)
        self.__dict__[attr] = val

    def toparam(self):
        self._buffs = []
        full_fmt = ""
        vals = []        
        for name, fmt, default in self._struct_items_:
            val = self.__dict__[name]
            if fmt == "z":
                fmt = "Pi"
                if val is None:
                    vals.append(0)
                    vals.append(0)
                else:
                    str_buf = array.array("c", val+'\0')
                    vals.append(str_buf.buffer_info()[0])
                    vals.append(len(val))
                    self._buffs.append(str_buf) # keep alive during the call.
            else:
                if val is None:
                    val = default
                vals.append(val)
            full_fmt += fmt		
        return struct.pack(*(full_fmt,) + tuple(vals))

class TBBUTTON(WIN32STRUCT):
    _struct_items_ = [
        ("iBitmap", "i", 0),
        ("idCommand", "i", 0),
        ("fsState", "B", 0),
        ("fsStyle", "B", 0),
        ("bReserved", "H", 0),
        ("dwData", "I", 0),
        ("iString", "z", None),
    ]

class Stub:
    """
    this class serves as a method stub,
    outputting debug info whenever the object
    is being called.
    """

    def __init__(self,name):
        self.name = name
        
    def __call__(self,*args):
        print 'STUB: ',self.name,args

class IEToolbarCtrl:
    """
    a tiny wrapper for our winapi-based
    toolbar control implementation.
    """
    def __init__(self,hwndparent):
        styles = win32con.WS_CHILD \
                | win32con.WS_VISIBLE \
                | win32con.WS_CLIPSIBLINGS \
                | win32con.WS_CLIPCHILDREN \
                | commctrl.TBSTYLE_LIST \
                | commctrl.TBSTYLE_FLAT \
                | commctrl.TBSTYLE_TRANSPARENT \
                | commctrl.CCS_TOP \
                | commctrl.CCS_NODIVIDER \
                | commctrl.CCS_NORESIZE \
                | commctrl.CCS_NOPARENTALIGN
        self.hwnd = win32gui.CreateWindow('ToolbarWindow32', None, styles,
                                          0, 0, 100, 100,
                                          hwndparent, 0, win32gui.dllhandle,
                                          None)
        win32gui.SendMessage(self.hwnd, commctrl.TB_BUTTONSTRUCTSIZE, 20, 0)

    def ShowWindow(self,mode):
        win32gui.ShowWindow(self.hwnd,mode)

    def AddButtons(self,*buttons):
        tbbuttons = ''
        for button in buttons:
            tbbuttons += button.toparam()
        return win32gui.SendMessage(self.hwnd, commctrl.TB_ADDBUTTONS,
                                    len(buttons), tbbuttons)

    def GetSafeHwnd(self):
        return self.hwnd

class IEToolbar:
    """
    The actual COM server class
    """
    _com_interfaces_ = _ietoolbar_com_interfaces_
    _public_methods_ = _ietoolbar_methods_
    _reg_clsctx_ = pythoncom.CLSCTX_INPROC_SERVER
    # if you copy and modify this example, be sure to change the clsid below
    _reg_clsid_ = "{F21202A2-959A-4149-B1C3-68B9013F3335}"
    _reg_progid_ = "PyWin32.IEToolbar"
    _reg_desc_ = 'PyWin32 IE Toolbar'

    def __init__( self ):
        # put stubs for non-implemented methods
        for method in self._public_methods_:
            if not hasattr(self,method):
                print 'providing default stub for %s' % method
                setattr(self,method,Stub(method))

    def GetWindow(self):
        return self.toolbar.GetSafeHwnd()

    def Load(self, stream):
        # called when the toolbar is loaded
        pass

    def Save(self, pStream, fClearDirty):
        # called when the toolbar shall save its information
        pass

    def CloseDW(self, dwReserved):
        del self.toolbar
        
    def ShowDW(self, bShow):
        if bShow:
            self.toolbar.ShowWindow(win32con.SW_SHOW)
        else:
            self.toolbar.ShowWindow(win32con.SW_HIDE)

    def on_first_button(self):
        print "first!"
        self.webbrowser.Navigate2('http://starship.python.net/crew/mhammond/')

    def on_second_button(self):
        print "second!"

    def on_third_button(self):
        print "third!"

    def toolbar_command_handler(self,args):
        hwnd,message,wparam,lparam,time,point = args
        if lparam == self.toolbar.GetSafeHwnd():
            self._command_map[wparam]()

    def SetSite(self,unknown):
        if unknown:
            # retrieve the parent window interface for this site
            olewindow = unknown.QueryInterface(pythoncom.IID_IOleWindow)
            # ask the window for its handle
            hwndparent = olewindow.GetWindow()

            # first get a command target
            cmdtarget = unknown.QueryInterface(axcontrol.IID_IOleCommandTarget)
            # then travel over to a service provider
            serviceprovider = cmdtarget.QueryInterface(pythoncom.IID_IServiceProvider)
            # finally ask for the internet explorer application, returned as a dispatch object
            self.webbrowser = win32com.client.Dispatch(serviceprovider.QueryService('{0002DF05-0000-0000-C000-000000000046}',pythoncom.IID_IDispatch))

            # now create and set up the toolbar
            self.toolbar = IEToolbarCtrl(hwndparent)

            buttons = [
                ('Visit PyWin32 Homepage',self.on_first_button),
                ('Another Button', self.on_second_button),
                ('Yet Another Button', self.on_third_button),
            ]

            self._command_map = {}
            # wrap our parent window so we can hook message handlers
            window = win32ui.CreateWindowFromHandle(hwndparent)

            # add the buttons
            for i in range(len(buttons)):
                button = TBBUTTON()
                name,func = buttons[i]
                id = 0x4444+i
                button.iBitmap = -2
                button.idCommand = id
                button.fsState = commctrl.TBSTATE_ENABLED
                button.fsStyle = commctrl.TBSTYLE_BUTTON
                button.iString = name
                self._command_map[0x4444+i] = func
                self.toolbar.AddButtons(button)
                window.HookMessage(self.toolbar_command_handler,win32con.WM_COMMAND)
        else:
            # lose all references
            self.webbrowser = None

    def GetClassID(self):
        return self._reg_clsid_

    def GetBandInfo(self, dwBandId, dwViewMode, dwMask):
        ptMinSize = (0,24)
        ptMaxSize = (2000,24)
        ptIntegral = (0,0)
        ptActual = (2000,24)
        wszTitle = 'PyWin32 IE Toolbar'
        dwModeFlags = DBIMF_VARIABLEHEIGHT
        crBkgnd = 0
        return (ptMinSize,ptMaxSize,ptIntegral,ptActual,wszTitle,dwModeFlags,crBkgnd)

# used for HKLM install
def DllInstall( bInstall, cmdLine ):
    comclass = IEToolbar

# register plugin
def DllRegisterServer():
    comclass = IEToolbar

    # register toolbar with IE
    try:
        print "Trying to register Toolbar.\n"
        hkey = _winreg.CreateKey( _winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar" )
        subKey = _winreg.SetValueEx( hkey, comclass._reg_clsid_, 0, _winreg.REG_BINARY, "\0" )
    except WindowsError:
        print "Couldn't set registry value.\nhkey: %d\tCLSID: %s\n" % ( hkey, comclass._reg_clsid_ )
    else:
        print "Set registry value.\nhkey: %d\tCLSID: %s\n" % ( hkey, comclass._reg_clsid_ )
    # TODO: implement reg settings for standard toolbar button

# unregister plugin
def DllUnregisterServer():
    comclass = IEToolbar

    # unregister toolbar from internet explorer
    try:
        print "Trying to unregister Toolbar.\n"
        hkey = _winreg.CreateKey( _winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Internet Explorer\\Toolbar" )
        _winreg.DeleteValue( hkey, comclass._reg_clsid_ )
    except WindowsError:
        print "Couldn't delete registry value.\nhkey: %d\tCLSID: %s\n" % ( hkey, comclass._reg_clsid_ )
    else:
        print "Deleting reg key succeeded.\n"

# entry point
if __name__ == '__main__':
    import win32com.server.register
    win32com.server.register.UseCommandLine( IEToolbar )
    
    # parse actual command line option
    if "--unregister" in sys.argv:
        DllUnregisterServer()
    else:
        DllRegisterServer()
else:
    # import trace utility for remote debugging
    import win32traceutil
