###############################################################################
# Name: ed_statbar.py                                                         #
# Purpose: Custom statusbar with builtin progress indicator                   #
# Author: Cody Precord <cprecord@editra.org>                                  #
# Copyright: (c) 2008 Cody Precord <staff@editra.org>                         #
# License: wxWindows License                                                  #
###############################################################################

"""
Custom StatusBar for Editra that contains a progress bar that responds to
messages from ed_msg to display progress of different actions.

@summary: Editra's StatusBar class

"""

__author__ = "Cody Precord <cprecord@editra.org>"
__svnid__ = "$Id: ed_statbar.py 60654 2009-05-16 16:06:36Z CJP $"
__revision__ = "$Revision: 60654 $"

#--------------------------------------------------------------------------#
# Imports
import wx
import wx.stc

# Editra Libraries
import ed_glob
import util
import ed_msg
from syntax.syntax import GetFtypeDisplayName
from eclib.pstatbar import ProgressStatusBar
from extern.decorlib import anythread

#--------------------------------------------------------------------------#
 
_ = wx.GetTranslation

#--------------------------------------------------------------------------#

class EdStatBar(ProgressStatusBar):
    """Custom status bar that handles dynamic field width adjustment and
    automatic expiration of status messages.

    """
    ID_CLEANUP_TIMER = wx.NewId()
    def __init__(self, parent):
        ProgressStatusBar.__init__(self, parent, style=wx.ST_SIZEGRIP)

        # Attributes
        self._pid = parent.GetId() # Save parents id for filtering msgs
        self._widths = list()
        self._cleanup_timer = wx.Timer(self, EdStatBar.ID_CLEANUP_TIMER)
        self._eolmenu = wx.Menu()

        # Setup
        self.SetFieldsCount(6) # Info, vi stuff, line/progress
        self.SetStatusWidths([-1, 90, 40, 40, 40, 155])
        self._eolmenu.Append(ed_glob.ID_EOL_MAC, u"CR",
                             _("Change line endings to %s") % u"CR")
        self._eolmenu.Append(ed_glob.ID_EOL_WIN, u"CRLF",
                             _("Change line endings to %s") % u"CRLF")
        self._eolmenu.Append(ed_glob.ID_EOL_UNIX, u"LF",
                             _("Change line endings to %s") % u"LF")

        # Event Handlers
        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.Bind(wx.EVT_TIMER, self.OnExpireMessage,
                  id=EdStatBar.ID_CLEANUP_TIMER)

        # Messages
        ed_msg.Subscribe(self.OnProgress, ed_msg.EDMSG_PROGRESS_SHOW)
        ed_msg.Subscribe(self.OnProgress, ed_msg.EDMSG_PROGRESS_STATE)
        ed_msg.Subscribe(self.OnUpdateText, ed_msg.EDMSG_UI_SB_TXT)
        ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_UI_NB_CHANGED)
        ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_FILE_SAVED)
        ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_UI_STC_LEXER)

    def __del__(self):
        """Unsubscribe from messages"""
        ed_msg.Unsubscribe(self.OnProgress)
        ed_msg.Unsubscribe(self.OnUpdateText)
        ed_msg.Unsubscribe(self.OnUpdateDoc)
        ProgressStatusBar.__del__(self)

    def __SetStatusText(self, txt, field):
        """Safe method to use for setting status text with CallAfter.
        @param txt: string
        @param field: int

        """
        try:
            ProgressStatusBar.SetStatusText(self, txt, field)
            self.AdjustFieldWidths()

            if field == ed_glob.SB_INFO:
                # Start the expiration countdown
                if self._cleanup_timer.IsRunning():
                    self._cleanup_timer.Stop()
                self._cleanup_timer.Start(10000, True)
        except wx.PyDeadObjectError:
            pass

    def AdjustFieldWidths(self):
        """Adust each field width of status bar basing on the field text
        @return: None

        """
        widths = []
        # Calculate required widths
        # NOTE: Order of fields is important
        for field in [ed_glob.SB_BUFF,
                      ed_glob.SB_LEXER,
                      ed_glob.SB_ENCODING,
                      ed_glob.SB_EOL,
                      ed_glob.SB_ROWCOL]:
            width = self.GetTextExtent(self.GetStatusText(field))[0]
            widths.append(width)

        # Adjust widths
        widths = [width + 20 for width in widths]
        widths.insert(0, -1)
        for idx, width in enumerate(list(widths)):
            if width == 20:
                widths[idx] = 0

        if widths[-1] < 155:
            widths[-1] = 155

        if widths != self._widths:
            self._widths = widths
            self.SetStatusWidths(self._widths)

    def OnExpireMessage(self, evt):
        """Handle Expiring the status message when the oneshot timer
        tells us it has expired.

        """
        if evt.GetId() == EdStatBar.ID_CLEANUP_TIMER:
            wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)
        else:
            evt.Skip()

    def OnLeftDClick(self, evt):
        """Handlers mouse left double click on status bar
        @param evt: Event fired that called this handler
        @type evt: 
        @note: Assumes parent is MainWindow instance

        """
        pt = evt.GetPosition()
        if self.GetFieldRect(ed_glob.SB_ROWCOL).Contains(pt):
            mw = self.GetParent()
            mpane = mw.GetEditPane()
            mpane.ShowCommandControl(ed_glob.ID_GOTO_LINE)
        else:
            evt.Skip()

    def OnLeftUp(self, evt):
        """Handle left clicks on the status bar
        @param evt: wx.MouseEvent

        """
        pt = evt.GetPosition()
        if self.GetFieldRect(ed_glob.SB_EOL).Contains(pt):
            rect = self.GetFieldRect(ed_glob.SB_EOL)
            self.PopupMenu(self._eolmenu, (rect.x, rect.y))
        else:
            evt.Skip()

    def OnProgress(self, msg):
        """Set the progress bar's state
        @param msg: Message Object

        """
        mdata = msg.GetData()
        # Don't do anything if the message is not for this frame
        if self._pid != mdata[0]:
            return

        mtype = msg.GetType()
        if mtype == ed_msg.EDMSG_PROGRESS_STATE:
            # May be called from non gui thread so don't do anything with
            # the gui here.
            self.SetProgress(mdata[1])
            self.range = mdata[2]
            if sum(mdata[1:]) == 0:
                self.Stop()
        elif mtype == ed_msg.EDMSG_PROGRESS_SHOW:
            if mdata[1]:
                self.Start(75)
            else:
                # TODO: findout where stray stop event is coming from...
                self.Stop()

    def OnUpdateDoc(self, msg):
        """Update document related fields
        @param msg: Message Object

        """
        self.UpdateFields()
        if msg.GetType() == ed_msg.EDMSG_UI_NB_CHANGED:
            wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)

    @anythread
    def DoUpdateText(self, msg):
        """Thread safe update of status text. Proxy for OnUpdateText because
        pubsub seems to have issues with passing decorator methods for
        listeners.
        @param msg: Message Object

        """
        # Only process if this status bar is in the active window and shown
        parent = self.GetTopLevelParent()
        if (parent.IsActive() or wx.GetApp().GetTopWindow() == parent):
            field, txt = msg.GetData()
            self.UpdateFields()
            wx.CallAfter(self.__SetStatusText, txt, field)

    def OnUpdateText(self, msg):
        """Update the status bar text based on the recieved message
        @param msg: Message Object

        """
        self.DoUpdateText(msg)

    def PushStatusText(self, txt, field):
        """Set the status text
        @param txt: Text to put in bar
        @param field: int

        """
        wx.CallAfter(self.__SetStatusText, txt, field)

    def SetStatusText(self, txt, field):
        """Set the status text
        @param txt: Text to put in bar
        @param field: int

        """
        wx.CallAfter(self.__SetStatusText, txt, field)

    def UpdateFields(self):
        """Update document fields based on the currently selected
        document in the editor.
        @postcondition: encoding and lexer fields are updated
        @todo: update when readonly hooks are implemented

        """
        nb = self.GetParent().GetNotebook()
        if nb is None:
            return

        try:
            cbuff = nb.GetCurrentCtrl()
            doc = cbuff.GetDocument()
            wx.CallAfter(self.__SetStatusText, doc.GetEncoding(),
                         ed_glob.SB_ENCODING)
            wx.CallAfter(self.__SetStatusText,
                         GetFtypeDisplayName(cbuff.GetLangId()),
                         ed_glob.SB_LEXER)

            eol = { wx.stc.STC_EOL_CR : u"CR",
                    wx.stc.STC_EOL_LF : u"LF",
                    wx.stc.STC_EOL_CRLF : u"CRLF" }
            wx.CallAfter(self.__SetStatusText,
                         eol[cbuff.GetEOLMode()],
                         ed_glob.SB_EOL)

        except wx.PyDeadObjectError:
            # May be called asyncronasly after the control is already dead
            return
