# PyUI
# Copyright (C) 2001-2002 Sean C. Riley
# 
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
# 
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import pyui.core
import win32con
import win32gui
import win32api
import win32ui

from pyui.locals import *

from pyui.rendererBase import RendererBase
from pyui.desktop import getDesktop, getRenderer

class rendererWin32(RendererBase):
    """win32 GDI renderer. Uses the python windows extensions for windows os interactions.
    """

    name = "Win32"
    
    def __init__(self, w, h, fullscreen):
        RendererBase.__init__(self, w, h, fullscreen)
        self.populateConstants()
        self.images = {}
        self.windows = {}
        self.eventFlag = 0
        self.first = 4
        self.nativeFrames = 1
        
        # win32 setup stuff
        wc = win32gui.WNDCLASS()
        self.hinst = wc.hInstance = win32api.GetModuleHandle(None)
        wc.lpszClassName = "PyUI App"
        wc.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW
        wc.hCursor = win32gui.LoadCursor( 0, win32con.IDC_ARROW )
        # TODO: make the icon work!
        #wc.hIcon = win32gui.LoadIcon(1, "pyui.ico")
        wc.hbrBackground = win32con.COLOR_WINDOW
        wc.lpfnWndProc = messageMap
        self.classAtom = win32gui.RegisterClass(wc)

        font_spec1 = {'name':'Arial', 'height':16}
        self.font1 = win32ui.CreateFont(font_spec1)

        font_spec2 = {'name':'Courier New', 'height':13}
        self.font2 = win32ui.CreateFont(font_spec2)

        self.windows = []
        
    def draw(self, windows, widgets=[]):
        """run the python widgets drawing code. There is no background in this renderer...
        This renderer draws windows when they are added and when they are dirty, and draws
        individual widgets when they are dirtied. The per-widget dirtying is important as
        win32 GDI calls are VERY slow - flashing behaviour happens when redrawing an entire
        window, so do that as seldom as possible.
        """
        if self.eventFlag == 0 and self.first == 0:
            return

        ## draw windows the first time we see them
        for window in windows:
            self.cdc = None
            self.currentWindow = window
            if not window in self.windows:
                self.windows.append(window)
                if window.show:
                   window.drawWindow(self)
                    #pass

        ## draw dirty widgets and the windows that love them
        for widget in widgets:
            self.cdc = None
            if widget.window:
                if not widget.window in self.windows:
                    continue
                # this is a widget, not a Window
                self.currentWindow = widget.window
                self.describeWindow(widget.window.handle, [])
                if widget.show:
                    widget.draw(self)
                if self.cdc and widget.window.handle and widget.window.show:
                    win32gui.ReleaseDC(widget.window.handle,self.hdc)                        
                    win32gui.UpdateWindow(widget.window.handle)
            elif isinstance(widget, pyui.widgets.Window):
                # this is a Window, not a widget
                self.cdc = None
                self.currentWindow = widget
                if widget.show:
                    widget.drawWindow(self)
                    if self.cdc:
                        win32gui.ReleaseDC(widget.handle,self.hdc)
                        if widget.handle and widget.show:
                            win32gui.UpdateWindow(widget.handle)
                
        self.dirtyRects = []
        self.eventFlag = 0

    def beginDraw(self):
        if self.cdc:
            return
        self.hdc = win32gui.GetWindowDC( self.currentWindow.handle)
        self.cdc = win32ui.CreateDCFromHandle(self.hdc)
        
    def populateConstants(self):
        """Populate pyui.constants with the values from msgs.py which are win32 message types.
        """
        pyui.locals.LMOUSEBUTTONDOWN = win32con.WM_LBUTTONDOWN
        pyui.locals.RMOUSEBUTTONDOWN = win32con.WM_RBUTTONDOWN
        pyui.locals.LMOUSEBUTTONUP   = win32con.WM_LBUTTONUP
        pyui.locals.RMOUSEBUTTONUP   = win32con.WM_RBUTTONUP
        pyui.locals.MOUSEMOVE        = win32con.WM_MOUSEMOVE
        pyui.locals.KEYDOWN          = win32con.WM_KEYDOWN
        pyui.locals.CHAR             = win32con.WM_CHAR
        pyui.locals.KEYUP            = win32con.WM_KEYUP

    def createWindow(self, title = None):
        # Create the Window.
        if title:
            style = win32con.WS_OVERLAPPED |   \
                    win32con.WS_SYSMENU |      \
                    win32con.WS_SIZEBOX|       \
                    win32con.WS_MINIMIZEBOX |  \
                    win32con.WS_MAXIMIZEBOX |  \
                    win32con.WS_VISIBLE
        else:
            style = win32con.WS_POPUP | win32con.WS_VISIBLE
            title = ""
        hwnd = win32gui.CreateWindow( self.classAtom, "PyUI: %s" % title, style, 0, 0, 100, 100, 0, 0, self.hinst, None)
        if title:
            win32gui.UpdateWindow(hwnd)
            win32gui.SetActiveWindow(hwnd)
        return hwnd

    def describeWindow(self, windowHandle, drawList):
        """the first rect is the extents of the window. resize & move it here.
        """
        win = getDesktop().findWindowByHandle(windowHandle)
        if not win:
            return
        if not windowHandle:
            return
        if len(drawList) == 0:
            win32gui.ShowWindow(windowHandle, 1)
        else:
            win32gui.ShowWindow(windowHandle, win32con.SW_HIDE)

        if win.__dict__.has_key("_first_"):
            return
        
        win32gui.MoveWindow(windowHandle, win.rect[0], win.rect[1], win.width, win.height, 1)
        win._first_ = 0
        win.setDirty(1)
    
    def moveToFront(self, windowHandle):
        win32gui.BringWindowToTop(windowHandle)

    def destroyWindow(self, windowHandle):
        win = getDesktop().findWindowByHandle(windowHandle)
        if windowHandle == self.currentWindow.handle:
            if self.cdc and self.hdc:
                win32gui.ReleaseDC(windowHandle,self.hdc)            
        win32gui.DestroyWindow(windowHandle)
        win.dirty = 0
        self.windows.remove(win)

    def moveWindow(self, windowHandle, x, y):
        pass
    
    ###############################################################################
    ### Draw Primatives functions
    ###############################################################################

    def drawRect(self, color, rect):
        """Fills a rectangle with the specified color."""
        self.beginDraw()
        self.cdc.FillSolidRect( (rect[0],
                            rect[1],
                            rect[0]+rect[2],
                            rect[1]+rect[3]), color )

    def drawLine(self, x1, y1, x2, y2, color):
        self.beginDraw()
        pen = win32ui.CreatePen( 0, 1, color)
        self.cdc.SelectObject(pen)
        self.cdc.MoveTo((x1, y1))
        self.cdc.LineTo((x2,y2))
        
    def drawText(self, text, pos, color, font = None):
        """Draws the text on the screen in the specified position. NOTE: need rect, not pos for this"""
        self.beginDraw()
        if font == 'fixed':
            self.cdc.SelectObject(self.font2)
        else:
            self.cdc.SelectObject(self.font1)
        self.cdc.SetBkMode(win32con.TRANSPARENT)
        self.cdc.SetTextColor(color)
        self.cdc.TextOut(pos[0], pos[1], text)
            
        
    def drawGradient(self, rect, c1, c2, c3, c4):
        """Draws a gradient rectangle"""        
        c = ((c1 & 0xFCFCFC) >> 2) + ((c2 & 0xFCFCFC) >> 2) + ((c3 & 0xFCFCFC) >> 2) + ((c4 & 0xFCFCFC) >> 2)
        self.beginDraw()
        self.cdc.FillSolidRect( (rect[0],
                            rect[1],
                            rect[0]+rect[2],
                            rect[1]+rect[3]), c1 )
        
    def drawImage(self, rect, filename):
        """Draws an image at a position. NOTE: should take a texture handle"""
        self.beginDraw()
        if not self.images.has_key(filename):
            self.loadImage(filename)
        bitmap = self.images[filename]
        if not bitmap:
            return
        (w,h) = bitmap.GetSize()
        src = (0,0, w, h)
        dest = (rect[0],
                rect[1],
                rect[0] + rect[2],
                rect[1] + rect[3])
        ret = bitmap.Paint(self.cdc, dest, src)

    def drawView(self, rect, handle):
        """Draws a viewport into a 3d World in the specified rectangle."""
        self.drawList.append( (pyui.locals.VIEW, rect[0], rect[1], rect[2], rect[3], handle) )
    
    def loadImage(self, filename, label = None):
        handle = win32ui.CreateBitmap()
        f = open(filename)
        try:
            handle.LoadBitmapFile(f)
        except:
            print "ERROR: reading bitmap", filename
            handle = None
        if label:
            self.images[label] = handle
        else:
            self.images[filename] = handle
        f.close()
        

    ###############################################################################
    ### actual drawing functions
    ###############################################################################

    def quit(self):
        print "win32 renderer Quitting."

    def packColor(self, r, g, b, a = 128):
        """pack the rgb triplet into a color
        """
        return (0 << 24) | (b << 16) | (g << 8) | r

    def unpackColor(self, color):
        r = (color & 0x000000FF)
        g = (color & 0x0000FF00) >> 8
        b = (color & 0x00FF0000) >> 16
        a = (color & 0xFF000000) >> 24
        return (r,g,b,a)

    def update(self):
        if self.first > 0:
            self.first = self.first - 1
            return
        #win32gui.WaitMessage()
        win32gui.PumpWaitingMessages()

    def doesDirtyRects(self):
        return 0

    def getTextSize(self, text, font = None):
        if font == 'fixed':
            return (len(text) * 8, 12)
        else:
            return (len(text) * 8, 18)


mousex = 0
mousey = 0

def gotEvent(hwnd, event, wParam, lParam):

    #print "gotEvent:", event, wParam, lParam 
    if event == win32con.WM_MOUSEMOVE:
        global mousex, mousey
        x = lParam & 0xffff
        y = lParam >> 16
        if x == mousex and y == mousey:
            return win32gui.DefWindowProc(hwnd, event, wParam, lParam)
        else:
            mousex = x
            mousey = y
            
    if event == win32con.WM_MOUSEMOVE or \
       event == win32con.WM_LBUTTONDOWN or \
       event == win32con.WM_LBUTTONUP or \
       event == win32con.WM_RBUTTONDOWN or \
       event == win32con.WM_RBUTTONUP:
        x = lParam & 0xffff
        y = lParam >> 16
        #print "Mouse Event: %d (%d,%d)" % (event, x, y)
        win = getDesktop().findWindowByHandle(hwnd)
        x = x + win._panel.rect[0]
        y = y + win._panel.rect[1]
        e = win.postEvent(event)
        e.pos =  (x, y)
        getRenderer().eventFlag = 1
        return 1

    if event == win32con.WM_KEYDOWN:
        getDesktop().postUserEvent(win32con.WM_KEYDOWN, 0, 0, wParam)
        getRenderer().eventFlag = 1
        if wParam == K_ESCAPE:
            getDesktop().quit()

    if event == win32con.WM_KEYUP:
        getDesktop().postUserEvent(win32con.WM_KEYUP, 0, 0, wParam)
        getRenderer().eventFlag = 1
        
    # This is the handler for characer keys.
    if event == win32con.WM_CHAR and wParam > 31 and wParam < 255:
        getDesktop().postUserEvent(win32con.WM_CHAR, 0, 0, chr(wParam))
        getRenderer().eventFlag = 1

    return win32gui.DefWindowProc(hwnd, event, wParam, lParam)


def onSize(hwnd, event, wParam, lParam):
    x = lParam & 0xffff
    y = lParam >> 16

    # calculate the size & pos of client area
    win = getDesktop().findWindowByHandle(hwnd)
    rect = win32gui.GetWindowRect(hwnd)
    crect = win32gui.GetClientRect(hwnd)
    
    width = (rect[2] - rect[0])
    height = (rect[3] - rect[1])
    clientX = width - crect[2] - 4
    clientY = height - crect[3] - 4

    if win:
        # setup the correct panel offsets
        win.panelOffsetLeft = 4
        win.panelOffsetRight = 4
        win.panelOffsetTop = clientY
        win.panelOffsetBottom = height - clientY - crect[3]
        win.resize(width, height)
    return 1

def onMove(hwnd, event, wParam, lParam):
    x = lParam & 0xffff
    y = lParam >> 16
    win = getDesktop().findWindowByHandle(hwnd)
    rect = win32gui.GetWindowRect(hwnd)

    if win:
        win.moveto(rect[0], rect[1])
    return 1

def onClose(hwnd, event, wParam, lParam):
    win = getDesktop().findWindowByHandle(hwnd)
    if win:
        win.destroy()
    if len(getDesktop().windows) == 0:
        getDesktop().quit()

def onPaint(hwnd, event, wParam, lParam):
    d = getDesktop()
    if not d:
        return win32gui.DefWindowProc(hwnd, event, wParam, lParam)        
    win = d.findWindowByHandle(hwnd)
    if win:
        win.setDirty(1)
        #getDesktop().addDirtyWidget(win)
        getRenderer().eventFlag = 1                
    return win32gui.DefWindowProc(hwnd, event, wParam, lParam)

messageMap = \
{
    win32con.WM_MOVE:onMove,
    win32con.WM_SIZE:onSize,
    win32con.WM_CLOSE:onClose,
    win32con.WM_KEYDOWN:gotEvent,
    win32con.WM_KEYUP:gotEvent,
    win32con.WM_CHAR:gotEvent,
    win32con.WM_MOUSEMOVE:gotEvent,
    win32con.WM_LBUTTONDOWN:gotEvent,
    win32con.WM_LBUTTONUP:gotEvent, 
    win32con.WM_LBUTTONDBLCLK:gotEvent, 
    win32con.WM_RBUTTONDOWN:gotEvent, 
    win32con.WM_RBUTTONUP:gotEvent, 
    win32con.WM_RBUTTONDBLCLK:gotEvent,
    win32con.WM_MBUTTONDOWN:gotEvent,
    win32con.WM_MBUTTONUP:gotEvent,
    win32con.WM_MBUTTONDBLCLK:gotEvent,
    win32con.WM_NCPAINT:gotEvent,
    win32con.WM_PAINT:onPaint
}
