###############################################################################
# File:        wxTools.py
# Appointment: contains helper tools to work with wxPython
# Project:     wx
# Author:      AR
# History:
# 25.09.2012 Created
###############################################################################
import wx
import zx

# Import publisher
if not zx.IsWindows():
    from wx.lib.pubsub import setuparg1
from wx.lib.pubsub import pub as Publisher


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Base class for Flex objects (lines/frames...ect)
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CFlexDrawBase:
    #---------------------------------------------------------------------------
    # aParent - window object
    # x,y - initial coordinates
    def __init__(self, aParent, aX, aY):
        # Create dc
        self._dc = wx.ClientDC(aParent)
        self._dc.SetPen(wx.Pen("blue", 1, wx.SOLID))
        self._dc.SetLogicalFunction(wx.INVERT)
        # Store initial coordinates
        self.StartX = aX
        self.StartY = aY

    #---------------------------------------------------------------------------
    # Standard dtor
    def __del__(self):
        self.ClearOld() # call virtual to clear ol picture
        del self._dc # clean up allocated resource
        
    #---------------------------------------------------------------------------
    # Return dc
    def GetDC(self):
        return self._dc
        
    #---------------------------------------------------------------------------
    # Virtual to be redefined - cleares old picture
    def ClearOld(self):
        pass
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Draws a flexible frame to select an area
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CFlexFrame(CFlexDrawBase):
    #---------------------------------------------------------------------------
    # aParent - window object
    # x,y - initial coordinates
    def __init__(self, aParent, aX, aY):
        CFlexDrawBase.__init__(self, aParent, aX, aY)
        # old coordinates
        self._oldX = self._oldY = -1
        
    #---------------------------------------------------------------------------
    # Redefined virtual
    def ClearOld(self):
        if self._oldX >= 0:
           self._drawRect(self.StartX, self.StartY, self._oldX, self._oldY)

    #---------------------------------------------------------------------------
    # Draws rectangle
    def _drawRect(self, aX1, aY1, aX2, aY2):
        self.GetDC().DrawLine(aX1, aY1, aX2, aY1)
        self.GetDC().DrawLine(aX2, aY1, aX2, aY2)
        self.GetDC().DrawLine(aX2, aY2, aX1, aY2)
        self.GetDC().DrawLine(aX1, aY2, aX1, aY1)

    #---------------------------------------------------------------------------
    # Draws rectangle from startX/Y to x/y
    # if aKeepAspectRatio is true, aspect ratio is kept for recangle

    def Draw(self, aX, aY):
        # Erase old frame
        self.ClearOld()

        # Draw new frame
        self._drawRect(self.StartX, self.StartY, aX, aY)
       
        # Store coordinates
        self._oldX = aX
        self._oldY = aY
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Draws a flexible line
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CFlexLine(CFlexDrawBase):
    #---------------------------------------------------------------------------
    # aParent - window object
    # x,y - initial coordinates
    def __init__(self, aParent, aX, aY):
        CFlexDrawBase.__init__(self, aParent, aX, aY)
        # old coordinates
        self._oldX = self._oldY = -1
        
    #---------------------------------------------------------------------------
    # Redefined virtual

    def ClearOld(self):
        if self._oldX >= 0:
           self.GetDC().DrawLine(self.StartX, self.StartY, self._oldX, self._oldY)

    #---------------------------------------------------------------------------
    # Draws rectangle from startX/Y to x/y
    # if aKeepAspectRatio is true, aspect ratio is kept for recangle
    def Draw(self, aX, aY):
        # Redraw the line
        self.ClearOld()
        self.GetDC().DrawLine(self.StartX, self.StartY, aX, aY)
        # Store coordinates
        self._oldX = aX
        self._oldY = aY

#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Scrolls window by dragging
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CMouseScroller:
    # aParent - window object
    def __init__(self, aParent):
        self._window = aParent
        self.Reset()

    # Resets oldies - to be called on EVT_LEFT_UP
    def Reset(self):
        self._oldX = self._oldY = -1

    # Scrolls using mouse coordinates aX/aY
    def Scroll(self, aX, aY):
        x, y = self._window.GetViewStart()
        if self._oldX != -1:
            self._window.Scroll(x + self._oldX - aX, y + self._oldY - aY)
        self._oldX = aX
        self._oldY = aY
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Yes/No question
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def AskYesNo(\
        aParent, aQuestion, aCaption,
        aPos = None,
        aButtons = wx.YES_NO | wx.ICON_QUESTION | wx.CANCEL):
    dlg = wx.MessageDialog(aParent, aQuestion, aCaption, aButtons, pos=aPos)
    result = dlg.ShowModal()
    dlg.Destroy()
    return result
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# ErrorReport
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def ErrorReport(aParent, aMessage, aCaption = "Error", aPos = None, aButtons = wx.OK | wx.ICON_ERROR):
    dlg = wx.MessageDialog(aParent, aMessage, aCaption, aButtons, pos=aPos)
    result = dlg.ShowModal()
    dlg.Destroy()
    return result
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Returns a list of 2 elements [(left, top), (width, height)]
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def LoadFramePosFromCfg(aCfgHolder, aFrame):
    width = int(aCfgHolder.GetValue("width", "0"))
    height = int(aCfgHolder.GetValue("height", "0"))
    left = int(aCfgHolder.GetValue("left", "-1"))
    top = int(aCfgHolder.GetValue("top", "-1"))
    return [(left, top) if left >= 0 and top >= 0 else None, (width, height) if width != 0 and height != 0 else None]
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Save frame's coordinates and width/height in cfg
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def SaveFramePosToCfg(aCfgHolder, aFrame):
    width, height = aFrame.GetSize()
    aCfgHolder.SetValue("width", str(width))
    aCfgHolder.SetValue("height", str(height))
    left, top = aFrame.GetPosition()
    aCfgHolder.SetValue("left", str(left))
    aCfgHolder.SetValue("top", str(top))
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    
#===============================================================================
# Frame class with saving/restoring size and position in API cfg
# The class uses API based config object
#===============================================================================
class CCfgFrame(wx.Frame):
    #---------------------------------------------------------------------------
    # aCfg - config object
    # aCfgPath - section path where to save data
    # aCfgName - subsection name for the frame
    def __init__(self, parent, aCfg, aCfgPath, aCfgName):
        # Init parent object
        wx.Frame.__init__(self, parent)

        # Load size and location from cfg
        self.Cfg = zx.CCfgHolder(aCfg, aCfgPath, aCfgName)
        p = LoadFramePosFromCfg(self.Cfg, self)
        self.SetPosition(p[0])
        self.SetSize(p[1])

        # In Close event - save cfg 
        self.Bind(wx.EVT_CLOSE, self.__OnClose)

        # Events to be redefined by user called in __OnClose to save config
        self.OnSaveCfg = None
        
        # Avoid closing until VETO
        self.NeedVetoOnClose = False
        
    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    def __OnClose(self, e):
        # Saving cfg
        SaveFramePosToCfg(self.Cfg, self)
        
        if self.OnSaveCfg is not None:
            self.OnSaveCfg()

        if e.CanVeto() and self.NeedVetoOnClose:
            print '### VETO ON CLOSE'
            self.Iconize()
            e.Veto()
            return

        e.Skip()
        
#===============================================================================



#===============================================================================
# Wrapper around ListCtrl
#===============================================================================
class CListCtrl(wx.ListCtrl):
    #---------------------------------------------------------------------------
    # Column abstraction
    #---------------------------------------------------------------------------
    class CColumn:
        # aGetTextFunc - a function f(row) returning a string text
        # aIsNumeric - used for sorting: if set in True, sorting will be done as for numeric values, else - as for strings
        def __init__(self, aGetTextFunc, aIsNumeric = False):
            self.GetTextFunc = aGetTextFunc
            self.IsNumeric = aIsNumeric
            
        def GetText(self, aRow, aCol):
            return self.GetTextFunc(aRow, aCol) if self.GetTextFunc is not None else "?"
    #---------------------------------------------------------------------------
    
    #---------------------------------------------------------------------------
    def __init__(self, parent):
        self.Columns = {} # map of columns <index:CColumn>
        wx.ListCtrl.__init__(\
            self, parent, -1, 
            style = wx.LC_VIRTUAL|wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VRULES|wx.LC_HRULES)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColumnEvent)
        
        # Map of user defined key events <key code: event function>
        self.SpecialKeyEvents = {}
        
        # Key events
        self.Bind(wx.EVT_LIST_KEY_DOWN, self.__OnKeyDown)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.__OnEdit)

        # Mouse right click
        self.Bind(wx.EVT_RIGHT_DOWN, lambda e: self.OnMouseRightClick(e))
        
        # Sorted indexes
        self.__SortedIndexes = []
        self.__SortedCol = -1
        self.__SortedInReverse = False
        self.__SortingAvailable = True
        
    #---------------------------------------------------------------------------
    # Returns True if dorting is available
    def IsSortingAvailable(self):
        return self.__SortingAvailable
        
    #---------------------------------------------------------------------------
    # Sets sorting availability (True/False)
    def SetSorting(self, aIsAvailable):
        self.__SortingAvailable = aIsAvailable

    #---------------------------------------------------------------------------
    # Return an index of item for mouse position x,y
    # Returns -1 if not found
    def FindPointedItem(self, aPos):
        x = aPos[0]
        y = aPos[1]
        for i in range (self.GetTopItem(), self.GetItemCount()):
            rect = self.GetItemRect(i)
            if not zx.IsWindows():
                rect.top -= rect.height # to consider the header
            if x >= rect.left and x <= rect.right and y >= rect.top and y <= rect.bottom:
                return i
        return -1
    
    #---------------------------------------------------------------------------
    # Helper: returns real item id from a row
    def RowToItem(self, aRow):
        return aRow if aRow >= len(self.__SortedIndexes) or aRow < 0 else self.__SortedIndexes[aRow]
    
    # Methods
    #========

    #---------------------------------------------------------------------------
    # This can be redefined and return a popup menu popping up on a cliecked item
    def GetPopupMenu(self):
        return None
    
    #---------------------------------------------------------------------------
    # To be used from outside - returns selected item after recalculation
    # self.GetFocusedItem() returns a position from top, i.e. 0,1,2... 
    # But real data item can be different due to sorting. To get index of data item use this method
    def GetSelectedDataItem(self):
        return self.RowToItem(self.GetFocusedItem())
    
    #---------------------------------------------------------------------------
    def AddColumn(self, aTitle, aWidth, aGetTextFunc = None, aAlign = wx.LIST_FORMAT_LEFT, aIsNumeric = False):
        index = self.GetColumnCount()
        info = wx.ListItem()
        info.SetMask(wx.LIST_MASK_TEXT)
        info.SetAlign(aAlign)
        info.SetText(aTitle)
        index = self.GetColumnCount()
        self.InsertColumnInfo(index, info)
        self.SetColumnWidth(index, aWidth)
        self.Columns[index] = CListCtrl.CColumn(aGetTextFunc, aIsNumeric)
    
    # Redefined virtuals from wx.ListCtrl
    #=============================================
    
    #---------------------------------------------------------------------------
    # Default method displays the column context
    def OnGetItemText(self, aRow, aCol):        
        return self.Columns[aCol].GetText(\
            self.RowToItem(aRow), aCol) if aCol in self.Columns else "?"

    # Virtuals to be redefined
    #=============================================
    # Called when editor is called
    def OnEdit(self, aPos):
        pass

    # Events
    #=======

    #---------------------------------------------------------------------------
    # Mouse event
    def OnMouseRightClick(self, event):
        pos = event.GetPosition()
        ind = self.FindPointedItem(pos)
        if ind >= 0:
            self.Select(ind)
            menu = self.GetPopupMenu()
            if menu is not None:
                self.PopupMenu(menu, pos)


    #---------------------------------------------------------------------------
    # Helper: returns a position of selected item
    def GetItemScreenPos(self, aItem):
        if aItem < 0:
            aItem = 0
        rect = self.GetItemRect(aItem)
        itemPos = self.GetItemPosition(aItem)
        # By some reasons self.GetHeaderHeight() is not available, possibly a version is to updates
        try:
            return self.ClientToScreenXY(itemPos[0], itemPos[1] + rect.GetHeight()*2)
        except:
            print "!!! ClientToScreenXY error:", aItem, itemPos[0], itemPos[1] + rect.GetHeight()*2            
            return (0, 0)
    
    #---------------------------------------------------------------------------
    def __OnEdit(self, e):
        self.OnEdit(self.GetItemScreenPos(self.GetFocusedItem()))
        self.Select(self.GetFocusedItem())
        e.Skip()

    
    #---------------------------------------------------------------------------
    def __OnKeyDown(self, e):
        c = e.GetKeyCode()
        # Special events
        if c in self.SpecialKeyEvents:
            self.SpecialKeyEvents[c](e)
        
    
    #---------------------------------------------------------------------------
    def __OnColumnEvent(self, e):
        if self.IsSortingAvailable():
            self.CurrentSortedCol = e.m_col
            
            # Sort data for requested column

            # Create a mocked list of tuples [(value,key)..] to sort keys by values
            d = []
            for i in range(self.GetItemCount()):
                strVal = self.Columns[e.m_col].GetText(i, e.m_col)
                if self.Columns[e.m_col].IsNumeric:
                    try:
                        d.append((float(strVal), i)) # can fail
                    except:
                        d.append((-99999.00, i))
                else:
                    d.append((strVal, i))
            
            # Recreate indexes for column
            if self.__SortedCol != e.m_col:
                self.__SortedCol = e.m_col
                self.__SortedInReverse = False
            else:
                self.__SortedInReverse = not self.__SortedInReverse

            self.__SortedIndexes = []
            for key in reversed(sorted(d, key = lambda e: e[0])) if self.__SortedInReverse else sorted(d, key = lambda e: e[0]):
                self.__SortedIndexes.append(key[1])
                
            self.Refresh()
        e.Skip()
#===============================================================================


#===============================================================================
# Helper class to be used in classes based on wxTools.CListCtrl
# Builds proper sized columns
#===============================================================================
class CColDataBuilder:
    # Column descriptor
    class CColDesc:
        def __init__(self, aTitle, aGetTextFunc, aAlign = wx.LIST_FORMAT_LEFT, aIsNumeric = False):
            self.Title = aTitle
            self.GetTextFunc = aGetTextFunc
            self.Align = aAlign
            self.IsNumeric = aIsNumeric
            self.MaxWidth = 0
    
    #---------------------------------------------------------------------------
    def __init__(self):
        # 2xdim array cols x rows
        self.__columns = []
        # Array of column descriptors
        self.__columnsDescs = []
       
    #---------------------------------------------------------------------------
    # Registeres a new column
    # aGetTextFunc should return a text value for given data element        
    def AddColumn(self, aTitle, aGetTextFunc, aAlign = wx.LIST_FORMAT_LEFT, aIsNumeric = False):
        self.__columnsDescs.append(self.CColDesc(aTitle, aGetTextFunc, aAlign, aIsNumeric))
        self.__columns.append([])
        
    #---------------------------------------------------------------------------
    # Returns a number of registered columns
    def GetColumnsCount(self):
        return len(self.__columnsDescs)
        
    #---------------------------------------------------------------------------
    # Returns a number of rows
    def GetRowsCount(self):
        return 0 if self.GetColumnsCount() == 0 else len(self.__columns[0])
        
    #---------------------------------------------------------------------------
    # Returns a column descriptor by index or None if index is out of range
    def GetColumnDesc(self, aColIndex):
        return None if aColIndex >= self.GetColumnsCount() or aColIndex < 0 else self.__columnsDescs[aColIndex]
        
    #---------------------------------------------------------------------------
    # Returns column value
    def GetValue(self, aRow, aCol):
        return None \
            if aCol >= self.GetColumnsCount() or aRow >= self.GetRowsCount() or aCol < 0 or aRow < 0 \
            else self.__columns[aCol][aRow]
        
    #---------------------------------------------------------------------------
    # Prepares data set from aDataList
    # Use aIsRefresh for refresh data only
    def Prepare(self, aDataList, aListCtrl = None, aIsRefresh = False):
        dc = wx.ClientDC(aListCtrl) if aListCtrl is not None else None
        def CalcTextWidth(aText):
            if dc is None:
                return 0
            w, h, d, e = dc.GetFullTextExtent(aText)
            return w 

        # Initial column widths
        for col in self.__columnsDescs:
            w = CalcTextWidth(col.Title) 
            if w > col.MaxWidth:
                col.MaxWidth = w

        # Clean up columns
        if aIsRefresh:
            for c in self.__columns:
                del c[:]

        # Loop for all elements: generate data
        for item in aDataList:
            # Generate text for each column
            i = 0
            for col in self.__columnsDescs:
                s = col.GetTextFunc(item)
                self.__columns[i].append(s)
                i += 1
                w = CalcTextWidth(s) 
                if w > col.MaxWidth:
                    col.MaxWidth = w

        # Add columns to the list ctrl
        if aListCtrl is not None:
            i = 0
            for c in self.__columnsDescs:
                if not aIsRefresh:
                    aListCtrl.AddColumn(\
                        c.Title, c.MaxWidth + 15, 
                        lambda row, col: self.__columns[col][row], c.Align, c.IsNumeric)
                else:
                    aListCtrl.SetColumnWidth(i, c.MaxWidth + 15)
                i += 1

#===============================================================================


#===============================================================================
# Common dialog
#===============================================================================
class CDialog(wx.Dialog):
    #---------------------------------------------------------------------------
    def __init__(self, parent, aTitle, aSize, aPos = None):
        wx.Dialog.__init__(self, parent, title=aTitle, size=aSize, pos=aPos)

        self.ControlsPanel = wx.Panel(self, style=wx.SP_3DBORDER)
        self.ControlsSizer = wx.BoxSizer(wx.VERTICAL)

        # Button panel
        self.ButtonsPanel = wx.Panel(self, -1, size=(0, 50), style=wx.SP_3DBORDER)
        self.ButtonsSizer = wx.BoxSizer(wx.HORIZONTAL)
        self.ButtonsSizer.AddStretchSpacer()        
        
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.ControlsPanel, 1, wx.EXPAND | wx.ALL)
        vbox.Add(self.ButtonsPanel, 0, wx.EXPAND | wx.LEFT | wx.RIGHT)
        
        self.SetSizer(vbox)
        
        # Registered controls to auto check changes <id, (control, oldValue)>
        self.__ControlValues = {}
        
    #---------------------------------------------------------------------------
    # Control Registration
    def RegisterControl(self, aControl):
        self.__ControlValues[aControl.GetId()] = (aControl, aControl.GetValue())
        

    #---------------------------------------------------------------------------
    # Returns control value by Id
    def GetControlValue(self, aControlId):
        return self.__ControlValues[aControlId][0].GetValue() if aControlId in self.__ControlValues else None
        
    #---------------------------------------------------------------------------
    # Checks for any changes amongst registered controls
    def CheckChanges(self):
        for ctrl in self.__ControlValues.itervalues():
            if ctrl[0].GetValue() != ctrl[1]:
                return True
        return False

    #---------------------------------------------------------------------------
    def AddButton(self, aCaption, aId = -1):
        b = wx.Button(self.ButtonsPanel, aId, label = aCaption)
        self.ButtonsSizer.Add(b)
        return b
        
    #---------------------------------------------------------------------------
    # Adds specific Ok button
    def AddExitButton(self, aCaption, aId):
        b = self.AddButton(aCaption, aId)
        b.Bind(wx.EVT_BUTTON, self.__OnClose)
        

    #---------------------------------------------------------------------------
    # This method should be called after adding all controls
    def Prepare(self):
        self.ControlsPanel.SetSizer(self.ControlsSizer)
        self.ButtonsSizer.AddStretchSpacer()        
        self.ButtonsPanel.SetSizer(self.ButtonsSizer)
        self.SetFocus()

    #---------------------------------------------------------------------------
    # Virtual to be redefined - called to save data
    def OnSave(self):
        pass     
                
    #---------------------------------------------------------------------------
    def __OnClose(self, e):
        b = e.GetEventObject()
        if b.GetId() == wx.ID_OK:
            self.OnSave()
        else:
            if b.GetId() == wx.ID_CANCEL and self.CheckChanges() and AskYesNo(self, 
                                                                              "Would you like to ignore changes?", 
                                                                              "Stop editting component") != wx.ID_YES:
                return
        e.Skip()
        

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


#===============================================================================
# Collection of windows
#===============================================================================
class CWindowsCollection:
    #---------------------------------------------------------------------------
    def __init__(self):
        # Collection <title: [window, creator, order of registration]>
        self.__collection = {}
        # unregister locker
        self.__unregisterLock = False
        # Order index
        self.__orderOfRegister = 0
        
    #---------------------------------------------------------------------------
    # Adds window to collection if it's not there yet, then shows it
    # Returns window object or None if faled to create
    def RegisterAndShow(self, aWindowCreator):
        # Create the window
        try:
            win = aWindowCreator()
        except:
            print "!!! CWindowsCollection.RegisterAndShow: failed to create a window"
            return None
        # Get title and check if the window already registered
        title = win.GetTitle()
        if not title in self.__collection:
            # Not registered yet - register
            self.__orderOfRegister += 1
            self.__collection[title] = [win, aWindowCreator, self.__orderOfRegister]
        # Show window
        try:
            self.__collection[title][0].Show()
            self.__collection[title][0].Raise()
            return self.__collection[title][0]
        except:
            # The object has been deleted without unregistration - do it explicitly and repeat
            del self.__collection[title] 
            return self.RegisterAndShow(aWindowCreator)
        
    #---------------------------------------------------------------------------
    # Removes window from collection
    def Unregister(self, aWindow):
        if not self.__unregisterLock:
            if aWindow.GetTitle() in self.__collection:
                del self.__collection[aWindow.GetTitle()]
            
    #---------------------------------------------------------------------------
    def CloseAll(self):
        self.__unregisterLock = True # to avoid changing collection
        for k,v in self.__collection.items():
            print 'closing', k
            try:
                v[0].Close()
            except:
                print 'CWindowsCollection.CloseAll()...Failed'
        self.__unregisterLock = False
        # Now - clear collection
        self.__collection.clear();

    #---------------------------------------------------------------------------
    # Returns a list of creators
    def GetCreators(self):
        sortedByOrder = {}
        for k,v in self.__collection.items():
            sortedByOrder[v[2]] = v[1]

        creators = []
        for k in sorted(sortedByOrder):
            creators.append(sortedByOrder[k])
        return creators

    #---------------------------------------------------------------------------
    # Recreates all windows from creators
    def ReCreate(self, aCreators):
        for c in aCreators:
            self.RegisterAndShow(c)


    #---------------------------------------------------------------------------
    # Returns window by title or None if not found
    def FindWindow(self, aTitle):
        return self.__collection[aTitle][0] if aTitle in self.__collection else None

    #---------------------------------------------------------------------------
    # Updates window registration counter to be the largest
    # to make it appear on top when ReCreate
    def Update(self, aWindow):
        if aWindow.GetTitle() in self.__collection:
            self.__orderOfRegister += 1
            self.__collection[aWindow.GetTitle()][2] = self.__orderOfRegister

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


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Sends message using publisher
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def PublisherSendMessage(aTopic):
    Publisher.sendMessage(aTopic)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Publisher message subscription
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def PublisherSubscribe(aListenerFunc, aTopic):
    Publisher.subscribe(aListenerFunc, aTopic)

def PublisherUnsubscribe(aListenerFunc, aTopic):
    Publisher.unsubscribe(aListenerFunc, aTopic)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

