#!/usr/bin/python

# tz_wx_widgets.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       December 1, 2007        bar
#       December 16, 2007       bar     whitespace
#       December 23, 2007       bar     kludge in icon logic (which should have been done differently, anyway.)
#       March 18, 2008          bar     exit param for bail_out()
#       May 15, 2008            bar     only fuss if time is off by more than 2 seconds
#       May 17, 2008            bar     email adr
#       May 20, 2009            bar     smarter command line help
#       September 16, 2010      bar     move write_window_image() here from elsewhere
#       November 18, 2010       bar     classes are objects now
#       April 14, 2011          bar     put the phone homer download to the app's desired directory if he has one spec'd
#       July 27, 2011           bar     diddle with the cmd line help OK button's bottom a bit under Linux so the OK button is fully shown
#       November 29, 2011       bar     pyflake cleanup
#       --eodstamps--
##      \file
#
#
#       Bunch of routines that are duped here and there.
#
#


import  cStringIO, zlib
import  os
import  re
import  sys
import  time

import  wx

import  phone_homer
import  tz_net_time
import  tz_parse_time
import  tzlib


## Used in window titles, etc.
OUR_TITLE   = "tz_wx_widgets"

## Used in window titles, etc.
OUR_NAME    = ""


def our_title(title = None) :
    """ Set the program's window title and name. """

    global  OUR_TITLE
    global  OUR_NAME

    ov  = OUR_TITLE

    if  title != None :
        OUR_TITLE   = title
        OUR_NAME    = OUR_TITLE.replace(" ", "").replace("-", "")

    return(ov)


def our_name(name = None) :
    """ Set the program's name. """

    global  OUR_NAME


    ov  = OUR_NAME

    if  name != None :
        OUR_NAME    = name

    return(ov)


our_title(OUR_TITLE)        # set the default name, too




def program_version_str(ident) :
    """
        Return a version number string, given an "ident",
        which looks like this, where 'whatever_the_program_name_is' and '0.00' are filled in with appropriate values:
        "<program_name>whatever_the_program_name_is</program_name><program_version>0.00</program_version>".

        In other words, we return "V0.00", given the above "ident" string.
    """

    return("V" + re.sub(r".*<program_version>([\d\.]+)</program_version>.*", r"\1", ident))



def frame_title(ident) :
    """ Return a string suitable for display in the program's title bar. """

    return(OUR_TITLE + " " + program_version_str(ident))




################################################################



#
#       From    http://www.py2exe.org/index.cgi/HowToDetermineIfRunningFromExe
#
import  imp

def main_is_frozen():
    return (hasattr(sys, "frozen") or           # new py2exe
           hasattr( sys, "importers")           # old py2exe
           or imp.is_frozen("__main__"))        # tools/freeze

def get_main_dir():
    if main_is_frozen():
        return(os.path.dirname(sys.executable))
    return(os.path.dirname(sys.argv[0]))




################################################################



def bail_out(s, exit = None) :
    """ Quit the program in a bit of an unfriendly way. """

    wx.MessageBox(s + "      ",   " " + OUR_TITLE + " ", wx.OK | wx.ICON_ERROR)


    if  not exit is None :
        sys.exit(exit)

    raise   "User abort"



def show_msg(msg, options = 0, title = None) :
    """
        Pop a OK/Cancel or whatever kind of dialog box to the user.
    """

    title   = title or OUR_NAME

    options = options or wx.OK
    dlg     = wx.MessageDialog(None, msg, title, options)
    retval  = dlg.ShowModal()
    dlg.Destroy()                                                   # this must be done or the program won't exit! (Several days of work to find this.)

    return(retval)




################################################################



def rewrap_win_label(win, width) :
    """
        Wrap a window/control's label, keeping in mind that it may already be wrapped for some other width.

        Return True if the label changed, False if it did not.
    """

    s   = win.GetLabel()
    s  = re.sub(r"\s+", " ", s)
    win.SetLabel(s)

    win.Wrap(width)

    if  s == win.GetLabel() :
        return(False)
    return(True)




################################################################


def cmd_line_help(help_str) :
    """ Pop a dialog box with a mono-spaced message showing command line options. """

    class a_help_dialog(wx.Dialog):
        """ Helper class. """

        def __init__(me, parent, msg, caption) :
            """ Create the dialog box. """

            wx.Dialog.__init__(me, parent, -1, caption)


            if  True :
                tb  = wx.BoxSizer(wx.HORIZONTAL)
                tv  = wx.BoxSizer(wx.VERTICAL)

                st  = wx.StaticText(me, -1)
                st.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
                st.SetLabel(msg)
                ( tx, ty )  = st.GetSize()
                st.Destroy()
                st  = wx.ScrollBar(me, -1, style = wx.SB_HORIZONTAL)
                ( x, sy )   = st.GetSize()
                st.Destroy()
                st  = wx.ScrollBar(me, -1, style = wx.SB_VERTICAL)
                ( sx, y )   = st.GetSize()
                st.Destroy()

                tw  = wx.TextCtrl(me, -1, style = wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_DONTWRAP | wx.TE_NOHIDESEL)
                tw.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, False))
                tw.SetBackgroundColour(me.GetBackgroundColour())            # ( 0xd0, 0xd0, 0xd0 ))
                tw.ChangeValue(msg)

                tv.Add(tw, 1, wx.EXPAND)
                tb.Add(tv, 1, wx.EXPAND)


            vb          = wx.BoxSizer(wx.VERTICAL)
            vb.AddSpacer(10)

            vb.Add(tb, 50, wx.EXPAND)

            vb.AddSpacer(20)

            bb          = wx.BoxSizer(wx.HORIZONTAL)
            bb.AddStretchSpacer(20)
            kb          = wx.Button(me, wx.ID_OK, "OK")
            ( x, by )   = kb.GetSize()
            bb.Add(kb)
            bb.AddStretchSpacer(20)

            vb.Add(bb, 2, wx.EXPAND)

            vspace      = 10
            if  sys.platform.find("linux") >= 0 :
                vspace  = 40
            vb.AddSpacer(vspace)


            dx          = dy    = 0
            ( dw, dh )  = wx.DisplaySize()
            dc          = wx.Display.GetCount()
            if  dc      :
                r       = wx.Display(0).GetGeometry()
                dx      = r.x
                dy      = r.y
                dw      = r.GetWidth()
                dh      = r.GetHeight()


            w   = min(tx + sx      + 20,          dw - 30)
            h   = min(ty + sy + by + 30 + vspace, dh - 30)
            me.SetSize((w, h))
            # me.CenterOnScreen(wx.BOTH)  # puts on zero-based display rather than 1st, primary display
            me.SetPosition((dx + max(0, (dw - w) / 2), dy + max(0, (dh - h) / 2)))

            me.SetSizer(vb)
            me.SetAutoLayout(True)
            me.Layout()



        pass    # a_help_dialog

    dlg = a_help_dialog(None, help_str, OUR_NAME + " Command Line Options")
    dlg.ShowModal()
    dlg.Destroy()




################################################################


def write_window_image(me, app) :
    """
        Write out a .png file of an image of 'me's (a Frame's) whole window.
        The 'app' has a routine, screen_shot(me, fname, typ) that does the actual file write (and annotates the image if it's a .png).

        Oddly enough, this logic seems to actually do a screen capture of the window area.
        So if another window is over the window, it will get in the .png file.
        That's bad. !!!!

        Under Linux it does not get the title bar area. !!!!
        And, it gets the file menu drop-down. !!!!
    """

    hx              = 0
    if  sys.platform.find("linux") >= 0 :
        hx          = wx.SYS_FRAMESIZE_Y + wx.SYS_MENU_Y
    wdc             = wx.WindowDC(me)
    ( w, h )        = wdc.GetSizeTuple()

    bm              = wx.EmptyBitmap(w, h + hx)
    dc              = wx.MemoryDC(bm)
    dc.Clear()

    dc.Blit(0, 0, w, h + hx, wdc, 0, 0 - hx, wx.COPY)

    #
    #   In case we want to write a .jpg
    #
    try :
        im  = bm.ConvertToImage()           # this is what might cause an exception, like when I'm fooling with the monitors
        im.SetOption("quality", "50")
    except :
        im  = None
        pass

    del(dc)
    del(bm)
    del(wdc)

    if  not im :
        show_msg("Sorry. I cannot make a screen shot of my window!")
    else :
        fname       =   None

        wildcard    =   "PNG (*.png)|*.png|"    \
                        "JPG (*.jpg)|*.jpg|"    \
                        "GIF (*.gif)|*.gif|"    \
                        "TIF (*.tif)|*.tif|"    \
                        "PNM (*.pnm)|*.pnm|"    \
                        "PCX (*.pcx)|*.pcx|"    \
                        "TGA (*.tga)|*.tga|"    \
                        "XPM (*.xpm)|*.xpm|"    \
                        "BMP (*.bmp)|*.bmp|"    \
                        "All files (*.*)|*.*"

        dflt_dir    =   os.getcwd()

        dlg         =   wx.FileDialog(me, message     = "Please tell me the file to save the window shot to ...",
                                          defaultDir  = dflt_dir,
                                          defaultFile = "",
                                          wildcard    = wildcard,
                                          style       = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_PREVIEW
                                     )

        if  dlg.ShowModal() == wx.ID_OK :
            fname   = dlg.GetPath()

        dlg.Destroy()

        me.Refresh()

        if  fname   :
            ext     = os.path.splitext(fname)[1].lower().lstrip(".")
            if  ext == "jpeg" : ext = "jpg"

            typ     = None
            hs      = im.GetHandlers()
            for h in hs :
                if  h.GetExtension() == ext :
                    typ = h.GetType()
                    break
                pass
            if  not typ :
                show_msg("Sorry, I don't know how to write such a (%s) file!" % ( ext ) )
            else :
                im.SaveFile(fname, typ)
                app.screen_shot(fname, typ)
            pass
        pass
    pass




################################################################



def set_icon(me, icon_data) :
    """ Set our icon at run time - not sure it's needed if py2exe is given an icon, but under interpretor it can affect some of the icon displays. """

    def getData(icon_data):
        return(zlib.decompress(icon_data))


    def getImage(icon_data):
        stream = cStringIO.StringIO(getData(icon_data))
        return(wx.ImageFromStream(stream))

    def getBitmap(icon_data):
        return(wx.BitmapFromImage(getImage(icon_data)))

    def getIcon(icon_data):
        icon    = wx.EmptyIcon()
        bm      = getBitmap(icon_data)
        icon.CopyFromBitmap(bm)
        return(icon)


    wx.InitAllImageHandlers()

    icon = getIcon(icon_data)
    me.SetIcon(icon)



def set_icon_uncompressed_data(me, icon_data) :
    """ Set our icon at run time - not sure it's needed if py2exe is given an icon, but under interpretor it can affect some of the icon displays. """

    def getImage(icon_data):
        stream = cStringIO.StringIO(icon_data)
        return(wx.ImageFromStream(stream))

    def getBitmap(icon_data):
        return(wx.BitmapFromImage(getImage(icon_data)))

    def getIcon(icon_data):
        icon    = wx.EmptyIcon()
        bm      = getBitmap(icon_data)
        icon.CopyFromBitmap(bm)
        return(icon)


    wx.InitAllImageHandlers()

    icon = getIcon(icon_data)
    me.SetIcon(icon)




################################################################



mchk    = None          # note: This must be persistent through the life of the program.

def program_already_running() :
    """ Return True if our program is already running. """

    global  mchk

    mchk    = wx.SingleInstanceChecker(OUR_TITLE + "-" + str(wx.GetUserId()))
    if  mchk.IsAnotherRunning() :
        show_msg("Already running!")

        return(True)

    return(False)




################################################################



class   a_phone_homer(object) :
    """ Class to check for program updates and tell the user if there is one. """

    def __init__(me, app, realm = "", user_name = "", password = "") :
        me.app          = app

        me.phone_homer  = None

        me.realm        = realm
        me.user_name    = user_name
        me.password     = password

        me.have_it      = False

        if  me.app.no_phone_home or me.app.show_info or me.app.phone_home_url or me.app.ident :             # cause an error if what we use from the owner app does not exist
            pass            # ok

        pass



    def realm_user_password(me, realm, user_name, password) :
        """
            Set a realm, user name, and password for the phone-homer.
        """

        me.realm        = realm
        me.user_name    = user_name
        me.password     = password



    def stop(me) :
        """ Stop the process. Return some not-None if we're busy. """

        ph              = me.phone_homer
        me.phone_homer  = None

        if  ph :
            ph.stop()

        return(ph)



    def start(me) :
        """ Do it. """

        if  not me.app.no_phone_home :
            me.stop()

            me.phone_homer  = phone_homer.a_phone_homer(me, me.app.phone_home_url, program_version_str(me.app.ident), dir_to_stash_new_version_files = getattr(me.app, 'phone_homer_to_dir', None), add_version_to_file_name = True, show_info = me.app.show_info)

            if  me.realm :
                me.phone_homer.realm_user_password(me.realm, me.user_name, me.password)

            me.phone_homer.start()
        pass



    def phone_homer_done(me, version, files, download_count) :
        """ Callback from the phone homer to tell us what's up. """

        me.phone_homer  = None
        if  version and (len(files) > 0) :      # to tell 'em just when we download:      and (download_count > 0) :
            show_msg("There is a new version (%s) of the program downloaded to %s" % ( version, os.path.abspath(files[0]) ) )
            me.have_it  = True
        pass



    def have_new_version(me) :
        """ Return True if we have a new version ready to go. """

        return(me.have_it)



    pass    # a_phone_homer




################################################################



class   a_net_timer(object) :
    """ Class to get the network GMT time and update the PC clock if the user desires. """

    def __init__(me, app, warn_if_no_time = True) :
        me.app              = app
        me.warn_if_no_time  = warn_if_no_time

        me.net_timer        = None

        me.gmt              = 0
        me.tick             = 0

        if  me.app.no_net_time :                                        # cause an error if what we use from the owner app does not exist
            pass            # ok

        pass



    def stop(me) :
        """ Stop the process. Return some not-None if we're busy. """

        nt              = me.net_timer
        me.net_timer    = None

        if  nt :
            nt.stop()

        return(nt)



    def start(me) :
        tzlib.elapsed_time()

        if  not me.app.no_net_time :
            me.stop()

            me.net_timer    = tz_net_time.a_net_timer(me)
            me.net_timer.start()
        pass



    def time(me) :
        """ If we have the true time, return it. Otherwise return 0. """

        if  me.gmt :
            if  0 < tzlib.elapsed_time() - me.tick <= 60 * 15 :
                return(me.gmt + (tzlib.elapsed_time() - me.tick))       # let the time be good for only 15 minutes (winxp hibernation resets elapsed_time counter back to zero, btw)
            pass

        return(0)



    def net_timer_done(me, gmt) :
        """ Callback from the net time logic to tell us what it got. """

        me.net_timer    = None
        me.gmt          = gmt
        me.tick         = tzlib.elapsed_time()
        pct             = time.time()

        if  gmt <= 0 :
            if  me.warn_if_no_time :
                show_msg("I could not find the GMT time out on the internet!\r\n"
                         "This means that I will do things relative to your PC's clock.\r\n\r\n"
                         "PC clocks are not notably accurate.\r\n"
                        )
            pass
        elif abs(pct - gmt) >= 2.5 :
            set_pc  = show_msg("Your PC's clock seems to be %s.\r\n"
                               "Should I attempt to fix it?\r\n" % ( tz_parse_time.time_diff_str(gmt, pct) ),
                               options  = wx.YES_NO | wx.NO_DEFAULT
                              )
            if  (set_pc == wx.ID_YES) and me.time() :                   # give 'em 15 minutes to respond
                if  not tz_net_time.set_system_time(me.time()) :
                    show_msg("I could not fix the system time. Sorry.")
                pass
            pass
        else :
            # print "time found", gmt, pct
            pass
        pass



    pass    # a_net_timer




################################################################



#
#
#       Test
#
#
if __name__ == '__main__' :

    pass

#
#
#
# eof

