__author__     = "Aaron Straup Cope"
__url__        = "http://www.mirrorproject.com/widget/"
__version__    = "1.0"
__cvsversion__ = "$Revision: 1.2 $"
__date__       = "$Date: 2004/04/19 02:39:20 $"
__copyright__  = "%s\n%s\n%s\n\n%s\n%s" % \
                 ("This application is licensed under the",
                  "Creative Commons Attribution-NonCommercial License",
                  "http://creativecommons.org/licenses/by-nc/1.0/",
                  "Individual photographs are copyright The Mirror Project",
                  "and their respective photographers, all rights reserved.")

__license__    = "This application is licensed under the Creative \
Commons Attribution-NonCommercial License. To view a copy of this \
license, visit http://creativecommons.org/licenses/by-nc/1.0/ or send \
a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, \
California 94305, USA. Individual photographs are copyright The Mirror \
Project and their respective photographers, all rights reserved."

from wxPython.wx import *
from cStringIO import StringIO
from time import sleep
from xmlrpclib import Server
from os import name

# this is an unfortunate hack to
# deal with some questionable design
# decisions on the server side...

from urllib import unquote_plus

URL_MIRRORPROJECT = "http://www.mirrorproject.com/xml/"

SECONDS_DELAY  = 30
SECONDS_SLEEP  = 0.01
COUNTER_DELAY  = int(1/SECONDS_SLEEP) * SECONDS_DELAY

# Insert hand-waving about internationalization
# here. Patches welcome...

TITLE_MP       = "The Mirror Project"
TITLE_APP      = "Random Image Widget"
TITLE_ABOUT    = "About"
TITLE_INFO     = "Image Info"

MENUBAR_FILE   = "&File"
MENUBAR_TOOLS  = "&Tools" 
MENUBAR_HELP   = "&Help"

MENU_ABOUT     = "&About"
MENU_PAUSE     = "&Pause"
MENU_RESUME    = "&Resume"
MENU_INFO      = "Image &Info"
MENU_QUIT      = "&Quit"

DESCRIBE_PAUSE   = "Pause"
DESCRIBE_RESUME  = "Resume"
DESCRIBE_INFO    = "Info"
DESCRIBE_ABOUT   = "More information about this program"
DESCRIBE_QUIT    = "Quit the program"

MSG_PAUSED       = "paused, press Ctrl-R to resume"
MSG_POLLING      = "asking for a new image"
MSG_PROCESSING   = ""

ERR_POLLING    = "failed to get image from server"
ERR_STRINGIFY  = "failed to read image from server"
ERR_STREAMIFY  = "failed to munge image"
ERR_DISPLAY    = "failed to set image"
ERR_IMAGEINFO  = "no image data"

DIALOG_ABOUT   = "%s\n%s\nversion %s\n\n%s" % \
	         (TITLE_MP,
		  TITLE_APP,
		  __version__,
		  __copyright__)

# These need better names...

STATUSTEXT = 0.8
BORDER     = 4

#

ID_APP     = wxNewId()
ID_PAUSE   = wxNewId()
ID_RESUME  = wxNewId()
ID_INFO    = wxNewId()
ID_ABOUT   = wxNewId()
ID_EXIT    = wxNewId()

class mpApp(wxApp):

    def OnInit(self):

        wxInitAllImageHandlers()
        self.Server = Server(URL_MIRRORPROJECT)

        frame = mpFrame(NULL, ID_APP, TITLE_MP)
        frame.Show(true)
        self.SetTopWindow(frame)

        frame.Panel.SetFocus()
	return true

    def MainLoop(self):
        frame = self.GetTopWindow()
        
        while frame and frame.Panel :
            
	    if frame.Paused == true:
                wxSafeYield(frame,true)

            else:
                frame.DisplayRandom(self.Server)
            
                for i in range(COUNTER_DELAY):
                    sleep(SECONDS_SLEEP)

                    if frame and frame.Panel:
                        if frame.Paused == true:
                            break
                        else:
                            wxYield()
                    else:
                        break               

class mpFrame(wxFrame):

    def __init__(self, parent, ID, title):

        wxFrame.__init__(self, parent, ID, title,
                         wxDefaultPosition,
			 wxSize(300,300))

        self.CreateStatusBar()

        FileMenu = wxMenu()

        FileMenu.Append(ID_PAUSE,
			MENU_PAUSE,
			DESCRIBE_PAUSE)

        FileMenu.Append(ID_RESUME,
			MENU_RESUME,
			DESCRIBE_RESUME)

        FileMenu.AppendSeparator()
                        
        FileMenu.Append(ID_EXIT,
			MENU_QUIT,
			DESCRIBE_QUIT)

        FileMenu.Enable(ID_RESUME,false)
        
        ToolsMenu = wxMenu()
        ToolsMenu.Append(ID_INFO,
			 MENU_INFO,
	                 DESCRIBE_INFO)	

        HelpMenu = wxMenu()
        HelpMenu.Append(ID_ABOUT,
			MENU_ABOUT,
	                DESCRIBE_ABOUT)
		    
        menuBar = wxMenuBar()
        menuBar.Append(FileMenu,  MENUBAR_FILE);
        menuBar.Append(ToolsMenu, MENUBAR_TOOLS);
        menuBar.Append(HelpMenu,  MENUBAR_HELP);

        self.SetMenuBar(menuBar)

        self.Panel   = mpPanel(self, -1)
        self.Image   = None
	self.Caption = None
	self.Data    = {}
	self.Paused  = false
        self.Die     = false
        
        EVT_MENU(self, ID_PAUSE,  self.Pause)
        EVT_MENU(self, ID_RESUME, self.Resume)
        EVT_MENU(self, ID_INFO,   self.Info)
        EVT_MENU(self, ID_ABOUT,  self.About)
        EVT_MENU(self, ID_EXIT,   self.Quit)

        EVT_CLOSE(self,self.Quit)
        
	# Doesn't appear to work under
	# py-wxPython (FreeBSD)
        EVT_MENU_CLOSE(self,self.SetCaption)

	EVT_KEY_DOWN(self.Panel,  self.KeyDown)
	
    def Yield(self):
        
        # This causes Windows to hang
        # FreeBSD hangs without it...

        if name == "nt":
            pass
        else:
            wxSafeYield(self,true)

    def ReportError(self,msg):
	if msg:
	   self.SetStatusText("[%s]" % self.PrepareStatusText(msg))
	else: 
	   self.SetStatusText("")

        self.Yield()

    def ReportActivity(self,msg):
	if msg:
	   self.SetStatusText("[%s]" % self.PrepareStatusText(msg))
	else: 
	   self.SetStatusText("")

        self.Yield()

    def Pause(self,event):

        if self.Paused == true:
            return true
        
        self.Paused = true
        self.ReportActivity(MSG_PAUSED)

        # http://wiki.wxpython.org/index.cgi/ \
        # wxPython_20Platform_20Inconsistencies

        menu = self.GetMenuBar().FindItemById(-1).GetMenu() 
        menu.Enable(ID_PAUSE,false)
        menu.Enable(ID_RESUME,true)
        
    def Resume(self,event):

        if self.Paused == false:
            return true
        
        self.Paused = false

        # http://wiki.wxpython.org/index.cgi/ \
        # wxPython_20Platform_20Inconsistencies
        
        menu = self.GetMenuBar().FindItemById(-1).GetMenu() 
        menu.Enable(ID_PAUSE,true)
        menu.Enable(ID_RESUME,false)

    def Info(self,event):

	if len(self.Data) == 0:
	   msg = ERR_IMAGEINFO
	else:
           msg = "%s\n" % (self.Data["who"])
           
           if self.Data["what"]:
               msg = "%s \"%s\"\n" % (msg,self.Data["what"])
              
           msg = "%s\n" % (msg)

           # god help me, there
           # *must* be a better
           # way...
           
           if self.Data["where"]:
               msg = "%s%s" % (msg,self.Data["where"])
              
           if self.Data["when"]:
               if self.Data["where"]:
                   msg = "%s, %s" % (msg,self.Data["when"])
               else:
                   msg = "%s%s" % (msg,self.Data["when"])
                   
        dlg = wxMessageDialog(self,
                              msg,
                              TITLE_INFO,
			      wxOK | wxICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def KeyDown(self,event):
        
	if event.ControlDown():
            keycode = event.GetKeyCode()
        else:
            event.Skip()
            return true
        
        if keycode <= 256:
            keyname = chr(keycode)
        else:
            event.Skip()
            return true
        
	if keyname == "A":
            self.About(event)
        elif keyname == "I":
            self.Info(event)
        elif keyname == "P":
            self.Pause(event)
        elif keyname == "R":
            self.Resume(event)
        elif keyname == "Q":
            self.Quit(event)
        else:
            event.Skip()
	
	return true

    
    def About(self, event):
        dlg = wxMessageDialog(self,
                              DIALOG_ABOUT,
                              TITLE_ABOUT,
			      wxOK | wxICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def Quit(self, event):

	# this is required by "nt"
	# and, I think, "os x"
	self.Panel.Destroy()

        self.Destroy()
        
    def DisplayRandom(self,server):

        self.ReportActivity(MSG_POLLING)

        try:
            resp = server.mirror.RandomImage()
        except:
            self.ReportError(ERR_POLLING)
            return false

        self.ReportActivity(MSG_PROCESSING)

        try:
            data = StringIO(resp["image"].data)         
        except:
            self.ReportError(ERR_STRINGIFY)
            return false

        try:
            image = wxImageFromStream(data)
        except:
            self.ReportError(ERR_STREAMIFY)
            return false

        try:
            self.Panel.Display(image)
        except:
            self.ReportError(ERR_DISPLAY)
            return false

	self.SetWindow(image)

        # the unquote_plus stuff is a hack;
        # see notes in import block above

	self.Data["who"]   = unquote_plus(resp["who"])
	self.Data["what"]  = unquote_plus(resp["what"])
	self.Data["where"] = unquote_plus(resp["where"])
	self.Data["when"]  = unquote_plus(resp["when"])
        
	if self.Data["what"]:
	   caption = "%s, %s" % (self.Data["who"],self.Data["what"])
	else:
	   caption = self.Data["who"]

	#

	caption = self.PrepareStatusText(caption,
					 int(image.GetWidth() * STATUSTEXT))

	#

	self.Caption = caption
	self.SetCaption()

        return true

    def SetCaption(self,event=None):
	if self.Caption:
	   self.SetStatusText(self.Caption)
	else:
	   self.SetStatusText("")

    def PrepareStatusText(self,txt,max=0):
	# Okay, now we have to make sure that the
	# text will fit in the status bar

        (txt_x,txt_y) = self.GetTextExtent(txt)

	if max == 0:
	   max = int(self.GetSize()[0] * STATUSTEXT)

	if txt_x > max:

	   while txt_x > max:

		# please make the python keeners
		# shut the fuck up about how great
		# their language is. my kingdom for
		# a substring function...

		txt = txt[0:len(txt) - 1]

		# just in case

		if len(txt) == 1:
		   txt = ""
		   break

	        (txt_x,txt_y) = self.GetTextExtent(txt)

	   txt = "%s..." % txt
	
	#

	return txt

    def SetWindow(self,image):

	scr_x = wxSystemSettings_GetMetric(wxSYS_SCREEN_X)
	scr_y = wxSystemSettings_GetMetric(wxSYS_SCREEN_Y)

	(sb_x,sb_y)   = self.GetStatusBar().GetSize()
	(mb_x,mb_y)   = self.GetMenuBar().GetSize()
        
	(pos_x,pos_y) = self.GetPosition()

        # must find a way to measure
	# the width of the various
	# windowing border elements
        
        count_border_offset = 2.25
        width_border_side   = 3

        if name == "nt":
            count_border_offset *= 2
            width_border_side   *= 2.75

            # windows returns something
            # cracked like (999334,0)
            # this is a bug...
            
            mb_y = 25

        #
        
	img_x  = image.GetWidth()
        img_x += (BORDER * count_border_offset)

	img_y  = image.GetHeight()
        img_y += (BORDER * width_border_side)

	# no comment

	if name == "nt":
	   img_x -= 1

        #
        
	win_x = int(img_x)
	win_y = int(img_y + sb_y + mb_y)

	new_x = pos_x
	new_y = pos_y

	move  = false

	#

	if (pos_x + win_x) > scr_x:
	   new_x = scr_x - (win_x + (BORDER * count_border_offset))
	   move  = true

	if (pos_y + win_y) > scr_y:
	   new_y = scr_y - (win_y + (BORDER * count_border_offset))
	   move  = true

	if move:
	   self.SetPosition((new_x,new_y))   

	#

	self.SetSize(wxSize(win_x,win_y))

class mpPanel(wxPanel):
    def __init__(self, parent, id):
        wxPanel.__init__(self, parent, id)
        self.Image = None

        EVT_PAINT(self, self.OnPaint)

    def Display(self, image):
        self.Image = image
        self.Refresh(True)

    def OnPaint(self, evt):

        if self.Image:

	   img_x  = self.Image.GetWidth()
	   img_y  = self.Image.GetHeight()

	   dc = wxPaintDC(self)
           dc.SetBrush(wxBrush("#000000"))
	   
	   # 3/7 should be computed somewhere...

	   dc.DrawRectangle(0,3,
			    img_x + (BORDER * 2),
			    img_y + (BORDER * 2))

           dc.DrawBitmap(self.Image.ConvertToBitmap(), BORDER,7)

# Make it so!
    
app = mpApp(0)
app.MainLoop()
