# Copyright (c) 2001, 2002, 2003 by Intevation GmbH
# Authors:
# Jonathan Coles <jonathan@intevation.de>
# Frank Koormann <frank.koormann@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.

__version__ = "$Revision: 1.36 $"

from  math import fabs, cos, pi

from Thuban import _

import resource

from wxPython.wx import *

from Thuban.Model.layer import BaseLayer
from Thuban.Model.map import Map
from Thuban.Model.classification import ClassGroup
from Thuban.Model.proj import PROJ_UNITS_DEGREES

from Thuban.Model.messages import \
    MAP_STACKING_CHANGED, MAP_LAYERS_ADDED, MAP_LAYERS_REMOVED, LAYER_CHANGED,\
    LAYER_VISIBILITY_CHANGED, TITLE_CHANGED

from Thuban.UI.messages import SCALE_CHANGED

from Thuban.UI.classifier import ClassDataPreviewer
from Thuban.UI.dock import DockPanel
from Thuban.UI.scalebar import ScaleBar

from Thuban.Lib.connector import ConnectorError

ID_LEGEND_TOP = 4001
ID_LEGEND_RAISE = 4002
ID_LEGEND_LOWER = 4003
ID_LEGEND_BOTTOM = 4004
ID_LEGEND_TREE = 4005
ID_LEGEND_PROPS = 4006
ID_LEGEND_SHOWLAYER = 4007
ID_LEGEND_HIDELAYER = 4008

ID_POPUP_TOP = 4501
ID_POPUP_UP  = 4502
ID_POPUP_DOWN = 4503
ID_POPUP_BOTTOM = 4504
ID_POPUP_PROPS  = 4506
ID_POPUP_VISIBLE = 4507
ID_POPUP_PROJ = 4509
ID_POPUP_REMOVE = 4510
ID_POPUP_SHOWTABLE = 4511

BMP_SIZE_W = 15
BMP_SIZE_H = 15

TOP_BMP = "top_layer"
RAISE_BMP = "raise_layer"
LOWER_BMP = "lower_layer"
BOTTOM_BMP = "bottom_layer"
SHOW_BMP  = "show_layer"
HIDE_BMP  = "hide_layer"
PROPS_BMP = "layer_properties"


class LegendPanel(DockPanel):

    def __init__(self, parent, map, mainWindow): 
        DockPanel.__init__(self, parent, -1)

        self.mainWindow = mainWindow
        self.parent = parent

        self.buttons = []

        panelBox = wxBoxSizer(wxVERTICAL)

        self.toolBar = wxToolBar(self, -1)
        self.toolBar.SetToolBitmapSize(wxSize(24, 24))

        bmp = resource.GetBitmapResource(TOP_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_TOP, bmp, 
            shortHelpString=_("Top Layer"))

        bmp = resource.GetBitmapResource(RAISE_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_RAISE, bmp, 
            shortHelpString=_("Raise Layer"))

        bmp = resource.GetBitmapResource(LOWER_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_LOWER, bmp, 
            shortHelpString=_("Lower Layer"))

        bmp = resource.GetBitmapResource(BOTTOM_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_BOTTOM, bmp, 
            shortHelpString=_("Bottom Layer"))

        bmp = resource.GetBitmapResource(SHOW_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_SHOWLAYER, bmp, 
            shortHelpString=_("Show Layer"))

        bmp = resource.GetBitmapResource(HIDE_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_HIDELAYER, bmp, 
            shortHelpString=_("Hide Layer"))

        bmp = resource.GetBitmapResource(PROPS_BMP, wxBITMAP_TYPE_XPM)
        self.toolBar.AddTool(ID_LEGEND_PROPS, bmp, 
            shortHelpString=_("Edit Layer Properties"))

        self.toolBar.Realize()
        panelBox.Add(self.toolBar, 0, wxGROW, 0)

        EVT_TOOL(self, ID_LEGEND_TOP, self._OnMoveTop)
        EVT_TOOL(self, ID_LEGEND_RAISE, self._OnMoveUp)
        EVT_TOOL(self, ID_LEGEND_LOWER, self._OnMoveDown)
        EVT_TOOL(self, ID_LEGEND_BOTTOM, self._OnMoveBottom)
        EVT_TOOL(self, ID_LEGEND_PROPS, self._OnProperties)
        EVT_TOOL(self, ID_LEGEND_SHOWLAYER, self._OnShowLayer)
        EVT_TOOL(self, ID_LEGEND_HIDELAYER, self._OnHideLayer)

        EVT_MENU(self, ID_POPUP_PROPS, self._OnProperties)
        EVT_MENU(self, ID_POPUP_TOP, self._OnMoveTop)
        EVT_MENU(self, ID_POPUP_UP, self._OnMoveUp)
        EVT_MENU(self, ID_POPUP_DOWN, self._OnMoveDown)
        EVT_MENU(self, ID_POPUP_BOTTOM, self._OnMoveBottom)
        EVT_MENU(self, ID_POPUP_VISIBLE, self._OnToggleVisibility)
        EVT_MENU(self, ID_POPUP_PROJ, self._OnProjection)
        EVT_MENU(self, ID_POPUP_REMOVE, self._OnRemoveLayer)
        EVT_MENU(self, ID_POPUP_SHOWTABLE, self._OnShowTable)
        
        self.tree = LegendTree(self, ID_LEGEND_TREE, map, mainWindow)

        panelBox.Add(self.tree, 1, wxGROW, 0)

        self.scalebarbitmap = ScaleBarBitmap(self, map, mainWindow) 
        panelBox.Add(self.scalebarbitmap, 0, wxGROW, 0)

        self.SetAutoLayout(True)
        self.SetSizer(panelBox)
        panelBox.SetSizeHints(self)


        self.panelBox = panelBox

        self.__EnableButtons(False)

        self.Create()

        EVT_CLOSE(self, self._OnClose)


    def GetMap(self):
        return self.tree.GetMap()

    def SetMap(self, map):
        self.tree.SetMap(map)
        self.scalebarbitmap.SetCanvas(self.mainWindow.canvas)

    def DoOnSelChanged(self, layer, group):

        ok = isinstance(layer, BaseLayer)
        self.__EnableButtons(ok)

        self.mainWindow.SelectLayer(layer)

    def DoOnProperties(self):
        list = self.tree.GetSelectedHierarchy()

        ok = isinstance(list[0], BaseLayer)
        if ok:
            self.mainWindow.OpenLayerProperties(list[0], list[1])

    def Destroy(self):
        self.__Close()

    def _OnProperties(self, event):
        self.DoOnProperties()

    def _OnMoveTop(self, event):
        self.tree.MoveCurrentItemTop()
        
    def _OnMoveUp(self, event): 
        self.tree.MoveCurrentItemUp()

    def _OnMoveDown(self, event):
        self.tree.MoveCurrentItemDown()

    def _OnMoveBottom(self, event):
        self.tree.MoveCurrentItemBottom()

    def _OnShowLayer(self, event):
        self.tree.DoOnShowLayer()
        pass

    #def Close(self, force = False):
        #DockPanel.Close(self, force)
        
    def _OnClose(self, event):
        self.__Close()

    def _OnHideLayer(self, event):
        self.tree.DoOnHideLayer()
        pass

    def _OnToggleVisibility(self, event):
        self.tree.ToggleVisibility()

    def _OnProjection(self, event):
        self.tree.LayerProjection()

    def _OnRemoveLayer(self, event):
        self.mainWindow.RemoveLayer()

    def _OnShowTable(self, event):
        self.mainWindow.LayerShowTable()

    def __EnableButtons(self, on):
        self.toolBar.EnableTool(ID_LEGEND_TOP, on)
        self.toolBar.EnableTool(ID_LEGEND_RAISE, on)
        self.toolBar.EnableTool(ID_LEGEND_LOWER, on)
        self.toolBar.EnableTool(ID_LEGEND_BOTTOM, on)
        self.toolBar.EnableTool(ID_LEGEND_SHOWLAYER,  on)
        self.toolBar.EnableTool(ID_LEGEND_HIDELAYER,  on)
        self.toolBar.EnableTool(ID_LEGEND_PROPS, on)

    def __Close(self):
        self.tree.Close()

class LegendTree(wxTreeCtrl):

    def __init__(self, parent, id, map, mainWindow):
        wxTreeCtrl.__init__(self, parent, id,
                            style = wxTR_DEFAULT_STYLE | wxTR_HIDE_ROOT,
                            size = (200, 200))

        self.mainWindow = mainWindow
        self.map = None
        self.parent = parent
        self.changing_selection = 0

        #
        # The image list used by the wxTreeCtrl causes problems when
        # we remove layers and/or change a classification because it
        # changes the image indices if you remove images from the list.
        # Rather than removing unused images we use this list to keep 
        # track of which indices are available in the image list 
        # (because of a previous removal) and then  replace those indices 
        # with new images rather than appending to the end of the image 
        # list (assuming there are any that are available).
        #
        self.availImgListIndices = []

        self.image_list = None
        self.emptyImageIndex = 0

        self.previewer = ClassDataPreviewer()

        self.preventExpandCollapse = False
        self.raiseProperties = False

        EVT_TREE_ITEM_ACTIVATED(self, ID_LEGEND_TREE, self._OnItemActivated)
        EVT_TREE_SEL_CHANGED(self, ID_LEGEND_TREE, self._OnSelChanged)
        EVT_TREE_ITEM_EXPANDING(self, ID_LEGEND_TREE, self.OnItemExpandCollapse)
        EVT_TREE_ITEM_COLLAPSING(self, ID_LEGEND_TREE, self.OnItemExpandCollapse)
        EVT_TREE_ITEM_RIGHT_CLICK(self, ID_LEGEND_TREE, self._OnRightClick)

        EVT_CLOSE(self, self._OnClose)

        self.SetMap(map)

    def _OnRightClick(self, event):
        """Select item and pop up a context menu"""

        # The pop up menu is related to the legend tree, so we have direct 
        # access on the tree items. The events issued by the menu are handled
        # by the legend panel, since most of the handlers are already
        # implemented there.

        # Update item selection to the right click
        item = event.GetItem()
        self.SelectItem(item)

        # Create the menu
        menu = wxMenu("", 0)

        # The "Visible" item is a special ...
        menuitem = wxMenuItem(menu, ID_POPUP_VISIBLE, _("Visible"), 
                                "", wxITEM_CHECK)
        menu.AppendItem(menuitem)
        layer, group = self.GetSelectedHierarchy()
        menuitem.Check(layer.Visible())

        menu.AppendSeparator()
        menu.Append(ID_POPUP_PROPS, _("&Properties..."))
        menu.Append(ID_POPUP_PROJ, _("Pro&jection..."))
        menu.Append(ID_POPUP_REMOVE, _("&Remove Layer"))
        menu.Append(ID_POPUP_SHOWTABLE, _("Show Ta&ble"))
        menu.AppendSeparator()
        menu.Append(ID_POPUP_TOP, _("Top Layer"))
        menu.Append(ID_POPUP_UP, _("Raise Layer"))
        menu.Append(ID_POPUP_DOWN, _("Lower Layer"))
        menu.Append(ID_POPUP_BOTTOM, _("Bottom Layer"))

        # Display the menu
        pos = event.GetPoint()
        shift = self.ClientToScreen((0,0))
        self.PopupMenu(menu, pos)

    def find_layer(self, layer):
        """Return the tree item for the layer"""
        root = self.GetRootItem()
        id, cookie = self.GetFirstChild(root, 0)
        while id.IsOk():
            if self.GetPyData(id) is layer:
                return id
            id, cookie = self.GetNextChild(root, cookie)
        return None

    def _OnClose(self, event):
        self.SetMap(None)

    def GetMap(self):
        return self.map

    def SetMap(self, map):

        sub_list = [(MAP_STACKING_CHANGED, self._OnMsgMapStackingChanged),
                    (MAP_LAYERS_ADDED, self._OnMsgMapLayersAdded),
                    (MAP_LAYERS_REMOVED, self._OnMsgMapLayersRemoved)]

        if self.map is not None:
            for msg, func in sub_list: self.map.Unsubscribe(msg, func)
            #self.mainWindow.application.Unsubscribe(SESSION_REPLACED, 
                #self._OnMsgMapsChanged)
            #try:
                #self.mainWindow.application.session.Unsubscribe(MAPS_CHANGED, 
                    #self._OnMsgMapsChanged)
            #except ConnectorError:
                #pass
            self.DeleteAllItems()
        
        self.map = map

        if self.map is not None:
            for msg, func in sub_list: self.map.Subscribe(msg, func)
            #self.mainWindow.application.session.Subscribe(MAPS_CHANGED, 
                #self._OnMsgMapsChanged)
            #self.mainWindow.application.Subscribe(SESSION_REPLACED, 
                #self._OnMsgMapsChanged)
            self.__FillTree(self.map)

    def MoveCurrentItemTop(self):
        layer, group = self.GetSelectedHierarchy()

        if layer is not None:
            self.map.MoveLayerToTop(layer)
        else:
            assert False, "Shouldn't be allowed."
            pass

    def MoveCurrentItemUp(self):
        layer, group = self.GetSelectedHierarchy()

        if layer is not None:
            self.map.RaiseLayer(layer)
        else:
            assert False, "Shouldn't be allowed."
            pass

    def MoveCurrentItemDown(self):
        layer, group = self.GetSelectedHierarchy()

        if layer is not None:
            self.map.LowerLayer(layer)
        else:
            assert False, "Shouldn't be allowed."
            pass

    def MoveCurrentItemBottom(self):
        layer, group = self.GetSelectedHierarchy()

        if layer is not None:
            self.map.MoveLayerToBottom(layer)
        else:
            assert False, "Shouldn't be allowed."
            pass

    def OnCompareItems(self, item1, item2):

        data1 = self.GetPyData(item1)
        data2 = self.GetPyData(item2)

        if isinstance(data1, BaseLayer):
            layers = self.map.Layers()
            return layers.index(data2) - layers.index(data1)
        else:
            return wxTreeCtrl.OnCompareItems(self, item1, item2)

    def DoOnShowLayer(self):
        layer, group = self.GetSelectedHierarchy()
        layer.SetVisible(True)

    def DoOnHideLayer(self):
        layer, group = self.GetSelectedHierarchy()
        layer.SetVisible(False)

    def ToggleVisibility(self):
        layer, group = self.GetSelectedHierarchy()

        layer.SetVisible(not layer.Visible())

    def LayerProjection(self):
        self.parent.mainWindow.LayerProjection()

    def Sort(self):
        self.SortChildren(self.GetRootItem())

    def GetSelectedHierarchy(self):
        id = self.GetSelection()

        if not id.IsOk():
            return (None, None)

        layer = self.GetPyData(id)
        group = None

        if isinstance(layer, ClassGroup):
            id = self.GetItemParent(id)
            assert id.IsOk()
            group = layer
            layer = self.GetPyData(id)

        return (layer, group)

    def _OnMsgMapsChanged(self):
        #print self.map is self.mainWindow.Map()
        self.SetMap(self.mainWindow.Map())
        
    def _OnSelChanged(self, event):
        # If we change the selection from normalize_selection do nothing.
        if self.changing_selection:
            return

        self.normalize_selection()
        self.__UpdateSelection()

    def normalize_selection(self):
        """Select the layer containing currently selected item"""
        # This is really a workaround for a bug in wx where deleting a
        # subtree with DeleteChildren does not update the selection
        # properly and can lead to segfaults later because the return
        # value of GetSelection points to invalid data.
        item = self.GetSelection()
        while item.IsOk():
            object = self.GetPyData(item)
            if isinstance(object, BaseLayer):
                break
            item = self.GetItemParent(item)
        else:
            # No layer was found in the chain of parents, so there's
            # nothing we can do.
            return

        self.changing_selection = 1
        try:
            self.SelectItem(item)
        finally:
            self.changing_selection = 0


    def OnItemExpandCollapse(self, event):
        if self.preventExpandCollapse:
            event.Veto()
            self.preventExpandCollapse = False

    def _OnItemActivated(self, event):
        # The following looks strange but is need under Windows to
        # raise the Properties on double-click: The tree control
        # always gets an Expanded / Collapsed event after the ItemActivated
        # on double click, which raises the main window again. We add a second
        # ItemActivated event to the queue, which simply raises the already
        # displayed window.
        if self.raiseProperties:
            self.parent.DoOnProperties()
            self.raiseProperties = False
        else:
            self.raiseProperties = True
            self.preventExpandCollapse = True
            self.parent.DoOnProperties()
            self.AddPendingEvent(event)

    def _OnMsgLayerChanged(self, layer):
        assert isinstance(layer, BaseLayer)

        id = self.find_layer(layer)
        assert id is not None

        self.__FillTreeLayer(id)
        self.__UpdateSelection()

    def _OnMsgMapStackingChanged(self, *args):
        self.Sort()
        id = self.GetSelection()

        if id.IsOk():
            self.EnsureVisible(id)
        self.__UpdateSelection()

    def _OnMsgMapLayersAdded(self, map):
        assert map is self.map

        # Build a dict with all layers known by the the tree as keys
        layers = {}
        root = self.GetRootItem()
        id, cookie = self.GetFirstChild(root, 0)
        while id.IsOk():
            layers[self.GetPyData(id)] = 1
            id, cookie = self.GetNextChild(root, cookie)

        # Add layers in the map but not in the dict
        i = 0
        for l in map.Layers():
            if not l in layers:
                self.__AddLayer(i, l)

        self.__UpdateSelection()

    def _OnMsgMapLayersRemoved(self, map):
        assert map is self.map

        layers = map.Layers()

        root = self.GetRootItem()
        id, cookie = self.GetFirstChild(root, 0)
        while id.IsOk():
            if self.GetPyData(id) not in layers:
                self.__RemoveLayer(id)
            id, cookie = self.GetNextChild(root, cookie)


        self.__UpdateSelection()

    def _OnMsgLayerVisibilityChanged(self, layer):
        assert isinstance(layer, BaseLayer)

        self.__ShowHideLayer(layer)
        self.__UpdateSelection()

    def _OnMsgLayerTitleChanged(self, layer):

        id = self.find_layer(layer)
        if id.IsOk():
            self.SetItemText(id, layer.Title())
        self.__UpdateSelection()

    def __UpdateSelection(self):
        layer, group = self.GetSelectedHierarchy()
        self.parent.DoOnSelChanged(layer, group)
        
    def __FillTree(self, map):

        self.Freeze()

        self.DeleteAllItems()

        if map.HasLayers():
            root = self.GetRootItem()
            for l in map.Layers():
                self.__AddLayer(0, l)

        self.Thaw()

    def __FillTreeLayer(self, pid):
        layer = self.GetPyData(pid)

        self.Freeze()

        self.DeleteChildren(pid)

        if layer.HasClassification():

            clazz = layer.GetClassification()

            shapeType = layer.ShapeType()

            show = layer.Visible()
            for g in clazz:
                if g.IsVisible():
                    id = self.AppendItem(pid, g.GetDisplayText())
                    self.SetPyData(id, g)
                    self.__SetVisibilityStyle(show, id)

                    bmp = self.__BuildGroupImage(g, shapeType)

                    if bmp is None:
                        self.SetItemImage(id, -1)
                        self.SetItemSelectedImage(id, -1)
                    else:
                        if self.availImgListIndices:
                            i = self.availImgListIndices.pop(0)
                            self.image_list.Replace(i, bmp)
                        else:
                            i = self.image_list.Add(bmp)

                        self.SetItemImage(id, i)
                        self.SetItemSelectedImage(id, i)

        self.Thaw()

    def __BuildGroupImage(self, group, shapeType):

        bmp = wxEmptyBitmap(BMP_SIZE_W, BMP_SIZE_H)
        #brush = wxBrush(Color2wxColour(item[1]), wxSOLID)
        dc = wxMemoryDC()
        dc.SelectObject(bmp)
        dc.Clear()

        self.previewer.Draw(dc, None, group.GetProperties(), shapeType)

        return bmp

    def DeleteAllItems(self):

        pid = self.GetRootItem()

        id, cookie = self.GetFirstChild(pid, 123)
        while id.IsOk():
            self.__RemoveLayer(id)
            id, cookie = self.GetNextChild(pid, cookie)

        wxTreeCtrl.DeleteAllItems(self)

    def __AddLayer(self, before, l):
        root = self.GetRootItem()
        id = self.InsertItemBefore(root, before,
                            l.Title(), 
                            self.mapImageIndex,
                            self.mapImageIndex)

        self.SetPyData(id, l)
        self.__SetVisibilityStyle(l.Visible(), id)

        self.__FillTreeLayer(id)
        self.Expand(id)

        l.Subscribe(LAYER_CHANGED, self._OnMsgLayerChanged)
        l.Subscribe(LAYER_VISIBILITY_CHANGED, 
                    self._OnMsgLayerVisibilityChanged)
        l.Subscribe(TITLE_CHANGED, self._OnMsgLayerTitleChanged)

    def __RemoveLayer(self, id):
        self.DeleteChildren(id)

        layer = self.GetPyData(id)
        layer.Unsubscribe(LAYER_CHANGED, 
                          self._OnMsgLayerChanged)
        layer.Unsubscribe(LAYER_VISIBILITY_CHANGED, 
                          self._OnMsgLayerVisibilityChanged)
        layer.Unsubscribe(TITLE_CHANGED, self._OnMsgLayerTitleChanged)

        self.Delete(id)

    def DeleteChildren(self, pid):
        id, cookie = self.GetFirstChild(pid, 123)
        while id.IsOk():
            self.availImgListIndices.append(self.GetItemImage(id))
            id, cookie = self.GetNextChild(pid, cookie)
        wxTreeCtrl.DeleteChildren(self, pid)

    def GetRootItem(self):
        root = wxTreeCtrl.GetRootItem(self)

        if not root.IsOk():
            self.image_list = wxImageList(BMP_SIZE_W, BMP_SIZE_H, False, 0)

            bmp = wxEmptyBitmap(BMP_SIZE_W, BMP_SIZE_H)
            dc = wxMemoryDC()
            dc.SelectObject(bmp)
            dc.SetBrush(wxBLACK_BRUSH)
            dc.Clear()
            dc.SelectObject(wxNullBitmap)

            self.emptyImageIndex = \
                self.image_list.AddWithColourMask(bmp, wxColour(0, 0, 0))

            bmp = resource.GetBitmapResource("legend_icon_layer", 
                                              wxBITMAP_TYPE_XPM)
            self.mapImageIndex = \
                self.image_list.Add(bmp)

            self.AssignImageList(self.image_list)
            self.availImgListIndices = []

            root = self.AddRoot("")

        return root

    def __SetVisibilityStyle(self, visible, id):
        font = self.GetItemFont(id)

        if visible:
            font.SetStyle(wxNORMAL)
            color = wxBLACK
        else:
            #font.SetStyle(wxITALIC)
            font.SetStyle(wxNORMAL)
            color = wxLIGHT_GREY

        self.SetItemTextColour(id, color)
        self.SetItemFont(id, font)
        
    def __ShowHideLayer(self, layer):
        parent = self.find_layer(layer)
        assert parent.IsOk()

        visible = layer.Visible()

        self.__SetVisibilityStyle(visible, parent)

        id, cookie = self.GetFirstChild(parent, 123)

        while id.IsOk():
            self.__SetVisibilityStyle(visible, id)
            id, cookie = self.GetNextChild(parent, cookie)
            
class ScaleBarBitmap(wxBoxSizer):

    def __init__(self, parent, map, mainWindow):
        # While the width is fixed, get the height _now_.
        dc = wxMemoryDC()
        textwidth, textheight = dc.GetTextExtent("%d"%0)
        self.width = 210
        self.height = textheight + 3*2 + 8

        wxBoxSizer.__init__(self, wxVERTICAL)
        bmp=wxEmptyBitmap(self.width, self.height)
        self.scalebarBitmap = wxStaticBitmap(parent, -1, bmp)
        self.Add(self.scalebarBitmap, 0, wxALIGN_CENTER|wxLEFT|wxTOP|wxRIGHT, 1)

        self.mainWindow = mainWindow
        self.parent = parent
        self.canvas = None
        self.SetCanvas(self.mainWindow.canvas)

    def SetCanvas(self, canvas):
        sub_list = [(SCALE_CHANGED, self._OnMsgScaleChanged)]

        if self.canvas is not None:
            for msg, func in sub_list: self.canvas.Unsubscribe(msg, func)
        
        self.canvas = canvas
        self.scalebar = ScaleBar(canvas.map)

        if self.canvas is not None:
            for msg, func in sub_list: self.canvas.Subscribe(msg, func)
            self.__SetScale(self.canvas.scale)

    def _OnMsgScaleChanged(self, scale):
        self.__SetScale(scale)

    def __SetScale(self, scale):
        bmp = wxEmptyBitmap(self.width, self.height)
        dc = wxMemoryDC()
        dc.SelectObject(bmp)
        dc.Clear()

        if self.canvas.map is not None \
            and self.canvas.map.projection is not None:

            # if we are using a projection with geographics coordinates
            # we need to change the scale value based on where we are
            # on the globe.
            if self.canvas.map.projection.GetProjectedUnits() \
                == PROJ_UNITS_DEGREES:

                width, height = self.canvas.GetSizeTuple()
                long, lat = self.canvas.win_to_proj(width/2, height/2)

                # slightly inaccurate, but if we are looking at
                # the north/south pole we could end up dividing by zero
                #
                # it shouldn't matter for our purposes that we ignore
                # the original sign of lat.
                if fabs(lat) > 89.9: lat = 89.9

                #
                # one degree is about 111,133m at the equator
                # we need to adjust that for latitude as 
                # we move north/south. use the center of the map
                # as the point to scale the length to.
                #
                scale = scale / (111133.0 * fabs(cos(lat * pi/180)))

            self.scalebar.DrawScaleBar(scale, dc, (0,0), dc.GetSizeTuple())

        self.scalebarBitmap.SetBitmap(bmp)

