
"""Classes for performing chores in Monarch application
"""

# built-in modules
import os, sys, string, traceback
# wxPython modules
from wxPython.wx import *
from wxPython.html import *
from wxPython.lib import splashscreen
from wxPython.lib import fancytext
from wxPython.lib.mixins.listctrl import wxColumnSorterMixin
from wxPython.lib.mixins.listctrl import wxListCtrlAutoWidthMixin
# custom modules
import localeutils, configmgr
import Communicators

__version__ = '$Revision: 1.4 $'

MA_CODENAME = 'Austin'
MA_AUTHOR = 'milkweed@sourceforge.jp'
MA_VERSTRING = '%s (%s)' %(__version__[1:-1], MA_CODENAME)
MA_APPTITLE = 'Monarch %s ' %(MA_VERSTRING)
MA_CONFPATHS = ['.monarch/monarch.conf', '.monarch.conf']
MA_APPDOMAIN = 'monarch'
MA_APPSPLASH = 'monarch_splash.png'

class MoApp(wxApp):

    """Handles associated data and event-loop for Monarch.

    OnInit(self) -- Initializer, called by wxWindows runtime.
    """

    def OnInit(self):
        """Initialize application. Overriden method. """
        wxInitAllImageHandlers()
        appdir = sys.path[0]

        # locale dependent messages
        try:
            tr = localeutils.gettrans(MA_APPDOMAIN, localeprefix=appdir)
            if tr:
                tr.install()
            else:
                wxMessageBox('Locale files not found.',
                             'Warning',
                             style=wxOK|wxICON_EXCLAMATION)
                # if there are no translations, simply returns the string.
                global _
                _ = lambda s: unicode(s)
        except:
            ty, va, tr = sys.exc_info()
            wxMessageBox('Errors in reading configuration.\n\n' \
                         + ''.join(traceback.format_exception(ty, va, tr)),
                         style=wxOK|wxICON_ERROR)
            return false
            
        # configuration
        self._config = None; self._confpath = ''
        try:
            for i in MA_CONFPATHS:
                ret = configmgr.find_config(i, appdir=appdir)
                if ret:
                    self._confpath, self._config = ret
                    break
            if not self._config:
                raise IOError
        except:
            ty, va, tr = sys.exc_info()
            wxMessageBox('Errors in reading configuration.\n\n' \
                         + ''.join(traceback.format_exception(ty, va, tr)),
                         'Fatal',
                         style=wxOK|wxICON_ERROR)
            return false
        
        # main-frame
        id = wxNewId()
        self._frame = MoMainFrame(None, id, MA_APPTITLE,
                                  owner=self, config=self._config)

        # show up the window
        self.SetTopWindow(self._frame)
        self._frame.Show(true)
        self._frame.Fit()

        return true

class MoSplash(splashscreen.SplashScreen):

    def __init__(self, duration=4000):
        sty = wxSIMPLE_BORDER|wxFRAME_NO_TASKBAR|wxSTAY_ON_TOP
        splashscreen.SplashScreen.__init__(self, None,
                                           bitmapfile=MA_APPSPLASH,
                                           duration=duration,
                                           callback=None, style=sty)
        st = wxStaticText(self, -1, '%s\n[%s]' %(MA_VERSTRING, MA_AUTHOR),
                          pos=(120, 90), size=(180,30))
        st.SetBackgroundColour(wxWHITE)
        st.SetFont(wxFont(8, wxSWISS, wxNORMAL, wxNORMAL, false, "Times"))
        
        

class MoMainFrame(wxFrame):

    """Application top-level GUI, handles most of actions.

    self.owner -- owner of the frame (target for application events).
    self.config -- ConfigParser object given from the owner.

    """

    def __init__(self, parent, ID, title, owner=None, config=None,
                 pos=wxDefaultPosition, size=wxDefaultSize,
                 style=wxDEFAULT_FRAME_STYLE, name="frame"):
        """Construct core GUI widgets and splash a title."""
        wxFrame.__init__(self, parent, ID, title, pos=pos, size=size,
                         style=style, name=name)
        self._owner = owner
        self._config = config

        # splashscreen
        self._splash = MoSplash()
        self._splash.Show(true)
        wxYield()

        try:
            fw = int(self._config.get('MainFrame', 'width'))
            fh = int(self._config.get('MainFrame', 'height'))
            fx = int(self._config.get('MainFrame', 'x'))
            fy = int(self._config.get('MainFrame', 'y'))
        except:
            fw, fh, fx, fy = (600, 400, 120, 50)

        self.SetDimensions(fx, fy, fw, fh)
        # splitter interface for left-right, top-bottom.
        try:
            tbsashpos = int(self._config.get('MainFrame', 'sashpos_tb'))
            lwsashpos = int(self._config.get('MainFrame', 'sashpos_lw'))
        except:
            raise
            tbsashpos, lwsashpos = (200, 300)

        id = wxNewId()
        self._lwSplit = wxSplitterWindow(self, id, style=wxSP_3D|wxNO_3D)
        id = wxNewId()
        spstyle = wxSP_NOBORDER|wxSP_3DSASH|wxNO_3D
        self._tbSplit = wxSplitterWindow(self._lwSplit, id, style=spstyle)

        # left board-menu note
        id = wxNewId()
        self._bmenuPane = wxPanel(self._lwSplit, id)
        id = wxNewId()
        self._bmenuTool = wxToolBar(self._bmenuPane, id)
        id = wxNewId()
        self._bmenuNote = wxNotebook(self._bmenuPane, id)
        sz = wxBoxSizer(wxVERTICAL)
        sz.Add(self._bmenuTool, 0, wxEXPAND|wxALL, 0)
        sz.Add(self._bmenuNote, 1, wxEXPAND|wxALL, 0)
        self._bmenuPane.SetSizer(sz)
        
        # right-top thread list
        id = wxNewId()
        self._thrPane = wxPanel(self._tbSplit, id)
        id = wxNewId()
        self._thrTool = wxToolBar(self._thrPane, id)
        id = wxNewId()
        self._thrNote = wxNotebook(self._thrPane, id)
        id = wxNewId()
        sz = wxBoxSizer(wxVERTICAL)
        sz.Add(self._thrTool, 0, wxEXPAND|wxALL, 0)
        sz.Add(self._thrNote, 1, wxEXPAND|wxALL, 0)
        self._thrPane.SetSizer(sz)
        
        # rigt-bottom post viewer
        id = wxNewId()
        self._logPane = wxPanel(self._tbSplit, id)
        id = wxNewId()
        self._logTool = wxToolBar(self._logPane, id)
        id = wxNewId()
        self._logNote = wxNotebook(self._logPane, id)
        id = wxNewId()
        sz = wxBoxSizer(wxVERTICAL)
        sz.Add(self._logTool, 0, wxEXPAND|wxALL, 0)
        sz.Add(self._logNote, 1, wxEXPAND|wxALL, 0)
        self._logPane.SetSizer(sz)
        
        # layout splitted panes
        self._tbSplit.SplitHorizontally(self._thrPane, self._logPane,
                                       sashPosition=tbsashpos)
        self._lwSplit.SplitVertically(self._bmenuPane, self._tbSplit,
                                     sashPosition=lwsashpos)

        # menu setup
        self._menuBar = MoMenuBar(self)
        # menu
        self.SetMenuBar(self._menuBar)

        # status bar
        id = wxNewId()
        self._statBar = wxStatusBar(self, id)
        self.SetStatusBar(self._statBar)

        # current communicator placeholder
        self._current_com = None
        self.loadMenu('TF2', '2ch', None)
        self._thrpage_dict = {}

    def OnQuit(self, evt):
        """Overrides wxApp, exit application loop explicitly."""
        self._owner.ExitMainLoop()
        
    def loadMenu(self, comclsname, menuname, args):
        comclass = Communicators.find_communicator(comclsname)
        if comclass:
            self._current_com = comclass(args)
            bmpage = MoBoardMenuTree(self._bmenuNote, -1, owner=self,
                                     communicator=self._current_com)
            bmpage.OnReload(None)
            self._bmenuNote.AddPage(bmpage, menuname)

    def OnConnect(self, evt):
        """Manage new connection.

        Placeholder. Not implemented yet.
        
        """
        # load connection dialog
##         dlg = MoSelectorDialog(self, -1, _('Connect'))
##         ret = dlg.ShowModal()
##         if ret == wxID_CANCEL:
##             pass
##         if ret == wxID_OK:
##             comclass = Communicators.find_communicator('TF2')
##             if comclass:
##                 self._current_com = comclass()
##                 bmpage = MoBoardMenuTree(self.bmenuNote, -1, owner=self,
##                                          communicator=_self.current_com)
##                 self._bmenuNote.AddPage(bmpage, 'TF2')
##                 bmpage.OnReload(None)
##         dlg.Destroy()

    def OnListThread(self, thrinfo):
        """Load thread-list page according to thrinfo values."""
        thrlabel, thrdata = thrinfo
        for i in xrange(self._thrNote.GetPageCount()):
            if self._thrNote.GetPage(i).thrdata == thrdata:
                self._thrNote.SetSelection(i)
                return
        # if no pages found
        id = wxNewId()
        page = MoThreadListMgr(self._thrNote, id, owner=self,
                               thrdata=thrdata,
                               communicator=self._current_com)
        self._thrNote.AddPage(page, thrlabel)
            

    def OnLoadThread(self, loginfo):
        """Load rendered html page according to loginfo values."""
        loglabel, logdata = loginfo
        for i in xrange(self._logNote.GetPageCount()):
            if self._logNote.GetPage(i).logdata == logdata:
                self._logNote.SetSelection(i)
                return
        # if no pages found
        id = wxNewId()
        log = MoHtmlWindow(self._logNote, id, logdata=logdata,
                           communicator=self._current_com)
        self._logNote.AddPage(log, loglabel)
        
class MoHtmlWindow(wxHtmlWindow):

    def __init__(self, parent, ID, logdata=None, communicator=None,
                 pos=wxDefaultPosition, size=wxDefaultSize,
                 style = wxHW_SCROLLBAR_AUTO,
                 name="MoHtmlWindow"):
        """"""
        wxHtmlWindow.__init__(self, parent, ID, pos=pos, size=size,
                              style=style, name=name)
        self._fontenum = wxFontEnumerator()
        defpropfont = "System"; propfont = defpropfont
        deffixfont = "Fixed"; fixfont = deffixfont
        if self._fontenum.EnumerateFacenames(wxFONTENCODING_SYSTEM):
            facenames = self._fontenum.GetFacenames()
            for fc in ["‚l‚r ‚oƒSƒVƒbƒN", "MS PGothic", "mona"]:
                if fc in facenames:
                    propfont = fc
                    try:
                        self.SetFonts(propfont, fixfont,
                                      [7, 8, 10, 12, 16, 22, 30])
                        break
                    except:
                        self.SetFonts(defpropfont, deffixfont,
                                      [7, 8, 10, 12, 16, 22, 30])
        self.logdata = logdata
        self.communicator = communicator
        self.renderLog()
        self.info = {}

    def renderLog(self):
        """Get html marked-up log and render it."""
        info = self.communicator.getThreadData(self.logdata)
        if info and info.has_key('log'):
            self.info = info
        self.SetPage(self.info['log'])
        
    def getLabel(self):
        """Return current label for the page."""
        if self.info.has_key('label'):
            return self.info['label']
    
class MoMenuBar(wxMenuBar):

    def __init__(self, owner, style=0):
        wxMenuBar.__init__(self, style=style)
        """Construct menubar and bind callbacks."""
        # menu's owner which implements individual menu handlers
        self.owner = owner
        
        # file menu
        self.fileMenu = wxMenu()
##         id = wxNewId()
##         self.file_open = wxMenu()
##         self.fileMenu.AppendMenu(id, _('Open'), self.file_open,
##                                  _('Open'))
        id = wxNewId()
        self.fileMenu.Append(id, _('Quit')+'\tCtrl-q')
        EVT_MENU(owner, id, owner.OnQuit)
        self.Append(self.fileMenu, _('File'))
        
        
##         # client menu
##         self.clntMenu = wxMenu()
##         id = wxNewId()
##         self.clntMenu.Append(id, _('Connect'))
##         EVT_MENU(owner, id, owner.OnConnect)
##         self.Append(self.clntMenu, _('Client'))

##         # edit menu
##         self.editMenu = wxMenu()
##         self.Append(self.editMenu, _('Edit'))

##         # display menu
##         self.dispMenu = wxMenu()
##         self.Append(self.dispMenu, _('Display'))

##         # tools menu
##         self.toolMenu = wxMenu()
##         self.Append(self.toolMenu, _('Tools'))

##         # help menu
##         self.helpMenu = wxMenu()
##         self.Append(self.helpMenu, _('Help'))

class MoSelectorDialog(wxDialog):

    def __init__(self, parent, ID, title,
                 pos=wxDefaultPosition, size=wxDefaultSize,
                 style=wxDEFAULT_DIALOG_STYLE):
        """Initialize dialog for selection of communicators."""
        wxDialog.__init__(self, parent, ID, title, pos=pos, size=size,
                          style=style, name="dialog")
        sz = wxBoxSizer(wxVERTICAL)
        
        btnsz = wxFlexGridSizer(cols=3)
        btnsz.AddGrowableCol(0)
        btnsz.Add(10, 3)
        cancelbtn = wxButton(self, wxID_CANCEL, _('Cancel'))
        btnsz.Add(cancelbtn, 1, wxEXPAND|wxALL, 3)
        connbtn = wxButton(self, wxID_OK, _('Connect'))
        btnsz.Add(connbtn, 1, wxEXPAND|wxALL, 3)
        sz.Add(btnsz, 0, wxEXPAND|wxALL, 3)
        self.SetSizer(sz)
        self.Fit()


class MoBoardMenuTree(wxTreeCtrl):

    def __init__(self, parent, ID, owner=None, pos=wxDefaultPosition,
                 size=wxDefaultSize, style=0, communicator=None):
        wxTreeCtrl.__init__(self, parent, ID, pos=pos, size=size,
                            style=style|wxTR_HAS_BUTTONS,
                            validator=wxDefaultValidator, name="")
        """Build minimum tree interface and bind callback."""
        self.owner = owner
        self.communicator = communicator
        self.treeroot = self.AddRoot('Root')
        EVT_TREE_ITEM_ACTIVATED(self, ID, self.OnItemActivated)

    def OnReload(self, evt):
        """(Re)Load board topics and grow the menu tree."""
        topics = self.communicator.getTopics()
        if topics:
            for topic in topics:
                node = self.AppendItem(self.treeroot, topic)
                subtopics = self.communicator.getSubTopics(topic)
                for label, url in subtopics:
                    subnode = self.AppendItem(node, label)
                    self.SetPyData(subnode, url)
            self.Expand(self.treeroot)

    def OnItemActivated(self, evt):
        """Get selected topic data and call owner's OnListThread."""
        if evt:
            topiclabel = self.GetItemText(evt.GetItem())
            topicdata = self.GetPyData(evt.GetItem())
            if topicdata:
                self.owner.OnListThread([topiclabel, topicdata])
            

class MoThreadListMgr(wxPanel, wxColumnSorterMixin):

    """Manages thread lisings with automated column adjustment and sorting.

    
    """

    COLINFO = [('ID', 0), ('Status', 0), ('Update', 0), ('Alive', 0),
               ('Priority', 0), ('Focus', 0), ('Posts', -1),
               ('Title', -1), ('Description', 10)]

    class ThreadList(wxListCtrl, wxListCtrlAutoWidthMixin):

        """Identical as wxListCtrl except automated column adjustment.

        """

        def __init__(self, parent, id, pos=wxDefaultPosition,
                     size=wxDefaultSize, style=0):
            """Build a simple wxListCtrl with automatic-width mixin."""
            wxListCtrl.__init__(self, parent, id, pos, size, style)
            wxListCtrlAutoWidthMixin.__init__(self)


    def __init__(self, parent, ID, owner=None, thrdata=None,
                 communicator=None, pos=wxDefaultPosition,
                 size=wxDefaultSize, style=0):
        """Build a panel containing ThreadList and bind callbacks for it."""
        wxPanel.__init__(self, parent, ID, pos, size, style)

        self.owner = owner
        self.thrdata = thrdata
        self.communicator = communicator
        id = wxNewId()
        lcstyles = wxLC_REPORT
        self.listCtrl = self.ThreadList(self, id, style=lcstyles)
        EVT_SIZE(self, self.OnSize)
        EVT_LIST_ITEM_ACTIVATED(self, id, self.OnItemActivated)

        sz = wxBoxSizer(wxVERTICAL)
        sz.Add(self.listCtrl, 1, wxALL|wxEXPAND, 0)
        self.SetSizer(sz)
        self.SetAutoLayout(true)
        self.itemDataMap = {}
        wxColumnSorterMixin.__init__(self, len(self.COLINFO))
        self.ListThreads()

    def OnItemActivated(self, evt):
        """Get selected thread data and call owner's OnLoadThread."""
        itemkey = self.listCtrl.GetItemData(evt.m_itemIndex)
        datlabel = self.itemDataMap[itemkey][-3]
        datname = self.itemDataMap[itemkey][-1]
        self.owner.OnLoadThread([datlabel, datname])
        
    def GetListCtrl(self):
        """Return ListCtrl on this panel. For column-sorter mixin."""
        return self.listCtrl

    def OnSize(self, evt):
        """Notify change of dimensions. For ListCtrl's auto-width mixin."""
        w, h = self.GetClientSizeTuple()
        self.listCtrl.SetDimensions(0, 0, w, h)

    def ListThreads(self):
        """Load a list of thread and set values into the list."""
        self.listCtrl.ClearAll()
        tlist = self.communicator.getThreads(data=self.thrdata)
        if not tlist:
            return
        # else
        for col in range(len(self.COLINFO)):
            label, width = self.COLINFO[col]
            self.listCtrl.InsertColumn(col, _(label))

        if self.itemDataMap:
            del self.itemDataMap
            self.itemDataMap = {}

        for i in xrange(len(tlist)):
            self.itemDataMap[i] = tlist[i]
            self.listCtrl.InsertStringItem(i, str(tlist[i][0]))
            self.listCtrl.SetItemData(i, i)
            for j in xrange(1, len(self.COLINFO)-1):
                self.listCtrl.SetStringItem(i, j, str(tlist[i][j]))

        for col in range(len(self.COLINFO)):
            label, width = self.COLINFO[col]
            self.listCtrl.SetColumnWidth(col, width)

