# Example of embedding CEF Python browser using wxPython library.
# This example has a top menu and a browser widget without navigation bar.

# To install wxPython on Linux type "sudo apt-get install python-wxtools".

# Tested configurations:
# - wxPython 2.8 on Linux
# - wxPython 3.0 on Windows
# - CEF Python v55.4+

import wx
from cefpython3 import cefpython as cef
import platform
import sys
import os

# Fix for PyCharm hints warnings
WindowUtils = cef.WindowUtils()

# Platforms
WINDOWS = (platform.system() == "Windows")
LINUX = (platform.system() == "Linux")
MAC = (platform.system() == "Darwin")

# Configuration
WIDTH = 800
HEIGHT = 600


def main():
    check_versions()
    sys.excepthook = cef.ExceptHook  # To shutdown all CEF processes on error
    settings = {}
    if WINDOWS:
        # High DPI support
        settings["auto_zooming"] = "system_dpi"
        # Embed DPI awareness xml manifest inside .exe (recommended,
        # most reliable) or call the SetProcessDpiAware function.
        # noinspection PyUnresolvedReferences, PyArgumentList
        cef.DpiAware.SetProcessDpiAware()
    cef.Initialize(settings=settings)
    app = CefApp(False)
    app.MainLoop()
    del app  # Must destroy before calling Shutdown
    cef.Shutdown()


def check_versions():
    print("[wxpython.py] CEF Python {ver}".format(ver=cef.__version__))
    print("[wxpython.py] Python {ver}".format(ver=sys.version[:6]))
    print("[wxpython.py] wxPython {ver}".format(ver=wx.version()))
    # CEF Python version requirement
    assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"


class MainFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
                          title='wxPython example', size=(WIDTH, HEIGHT))
        self.browser = None

        self.setup_icon()
        self.create_menu()
        self.Bind(wx.EVT_CLOSE, self.OnClose)

        # Set wx.WANTS_CHARS style for the keyboard to work.
        # This style also needs to be set for all parent controls.
        self.browser_panel = wx.Panel(self, style=wx.WANTS_CHARS)
        self.browser_panel.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.browser_panel.Bind(wx.EVT_SIZE, self.OnSize)

        # Must show so that handle is available when embedding browser
        self.Show()
        self.embed_browser()

    def setup_icon(self):
        icon_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                 "resources", "wxpython.png")
        if os.path.exists(icon_file):
            icon = wx.IconFromBitmap(wx.Bitmap(icon_file, wx.BITMAP_TYPE_PNG))
            self.SetIcon(icon)

    def create_menu(self):
        filemenu = wx.Menu()
        filemenu.Append(1, "Some option")
        filemenu.Append(2, "Another option")
        aboutmenu = wx.Menu()
        aboutmenu.Append(1, "Yet another option")
        menubar = wx.MenuBar()
        menubar.Append(filemenu, "&File")
        menubar.Append(aboutmenu, "&About")
        self.SetMenuBar(menubar)

    def embed_browser(self):
        window_info = cef.WindowInfo()
        window_info.SetAsChild(self.browser_panel.GetHandle())
        self.browser = cef.CreateBrowserSync(window_info,
                                             url="https://www.google.com/")
        self.browser.SetClientHandler(FocusHandler())

    def OnSetFocus(self, _):
        if not self.browser:
            return
        if WINDOWS:
            WindowUtils.OnSetFocus(self.browser_panel.GetHandle(),
                                   0, 0, 0)
        self.browser.SetFocus(True)

    def OnSize(self, _):
        if not self.browser:
            return
        if WINDOWS:
            WindowUtils.OnSize(self.browser_panel.GetHandle(),
                               0, 0, 0)
        elif LINUX:
            (x, y) = (0, 0)
            (width, height) = self.browser_panel.GetSizeTuple()
            self.browser.SetBounds(x, y, width, height)
        self.browser.NotifyMoveOrResizeStarted()

    def OnClose(self, event):
        # In cefpython3.wx.chromectrl example calling browser.CloseBrowser()
        # and/or self.Destroy() in OnClose is causing crashes when
        # embedding multiple browser tabs. The solution is to call only
        # browser.ParentWindowWillClose. Behavior of this example
        # seems different as it extends wx.Frame, while ChromeWindow
        # from chromectrl extends wx.Window. Calling CloseBrowser
        # and Destroy does not cause crashes, but is not recommended.
        # Call ParentWindowWillClose and event.Skip() instead. See
        # also Issue #107: https://github.com/cztomczak/cefpython/issues/107
        self.browser.ParentWindowWillClose()
        event.Skip()

        # Clear all browser references for CEF to shutdown cleanly
        del self.browser


class FocusHandler(object):

    def __init__(self):
        pass

    def OnTakeFocus(self, **kwargs):
        # print("[wxpython.py] FocusHandler.OnTakeFocus, next={next}"
        #       .format(next=kwargs["next_component"]]))
        pass

    def OnSetFocus(self, **kwargs):
        # source_enum = {cef.FOCUS_SOURCE_NAVIGATION: "navigation",
        #                cef.FOCUS_SOURCE_SYSTEM:     "system"}
        # print("[wxpython.py] FocusHandler.OnSetFocus, source={source}"
        #       .format(source=source_enum[kwargs["source"]]))
        # return False
        pass

    def OnGotFocus(self, browser, **_):
        # Temporary fix for focus issues on Linux (Issue #284).
        # If this is not applied then when switching to another
        # window (alt+tab) and then back to this example, keyboard
        # focus becomes broken, you can't type anything, even
        # though a type cursor blinks in web view.
        if LINUX:
            print("[wxpython.py] FocusHandler.OnGotFocus:"
                  " keyboard focus fix (#284)")
            browser.SetFocus(True)


class CefApp(wx.App):

    def __init__(self, redirect):
        self.timer = None
        self.timer_id = 1
        super(CefApp, self).__init__(redirect=redirect)

    def OnInit(self):
        self.create_timer()
        frame = MainFrame()
        self.SetTopWindow(frame)
        frame.Show()
        return True

    def create_timer(self):
        # See also "Making a render loop":
        # http://wiki.wxwidgets.org/Making_a_render_loop
        # Another way would be to use EVT_IDLE in MainFrame.
        self.timer = wx.Timer(self, self.timer_id)
        self.timer.Start(10)  # 10ms
        wx.EVT_TIMER(self, self.timer_id, self.on_timer)

    def on_timer(self, _):
        cef.MessageLoopWork()

    def OnExit(self):
        self.timer.Stop()


if __name__ == '__main__':
    main()
