view orpg/map/_canvas.py @ 183:0d9b746b5751 beta

Traipse Beta 'OpenRPG' {100115-00} Traipse is a distribution of OpenRPG that is designed to be easy to setup and go. Traipse also makes it easy for developers to work on code without fear of sacrifice. 'Ornery-Orc' continues the trend of 'Grumpy' and adds fixes to the code. 'Ornery-Orc's main goal is to offer more advanced features and enhance the productivity of the user. Update Summary (Beta) New Features: Added Bookmarks Added 'boot' command to remote admin Added confirmation window for sent nodes Minor changes to allow for portability to an OpenSUSE linux OS Miniatures Layer pop up box allows users to turn off Mini labels, from FlexiRPG Zoom Mouse plugin added Images added to Plugin UI Switching to Element Tree Map efficiency, from FlexiRPG Added Status Bar to Update Manager New TrueDebug Class in orpg_log (See documentation for usage) Portable Mercurial Tip of the Day added, from Core and community New Reference Syntax added for custom PC sheets New Child Reference for gametree New Parent Reference for gametree New Gametree Recursion method, mapping, context sensitivity, and effeciency.. New Features node with bonus nodes and Node Referencing help added Dieroller structure from Core New DieRoller portability for odd Dice Added 7th Sea die roller; ie [7k3] = [7d10.takeHighest(3).open(10)] New 'Mythos' System die roller added Added new vs. die roller method for WoD; ie [3v3] = [3d10.vs(3)]. Included for Mythos roller also New Warhammer FRPG Die Roller (Special thanks to Puu-san for the support) New EZ_Tree Reference system. Push a button, Traipse the tree, get a reference (Beta!) Fixes: Fix to Text based Server Fix to Remote Admin Commands Fix to Pretty Print, from Core Fix to Splitter Nodes not being created Fix to massive amounts of images loading, from Core Fix to Map from gametree not showing to all clients Fix to gametree about menus Fix to Password Manager check on startup Fix to PC Sheets from tool nodes. They now use the tabber_panel Fix to Whiteboard ID to prevent random line or text deleting. Fixes to Server, Remote Server, and Server GUI Fix to Update Manager; cleaner clode for saved repositories Fixes made to Settings Panel and now reactive settings when Ok is pressed Fixes to Alternity roller's attack roll. Uses a simple Tuple instead of a Splice Fix to Use panel of Forms and Tabbers. Now longer enters design mode Fix made Image Fetching. New fetching image and new failed image Modified ID's to prevent non updated clients from ruining the fix. default_manifest.xml renamed to default_upmana.xml
author sirebral
date Fri, 15 Jan 2010 23:01:42 -0600
parents 4385a7d0efd1
children
line wrap: on
line source

from threading import Lock
import mimetypes
import xml.dom.minidom as minidom

import wx

import orpg.dirpath
from orpg.orpgCore import *
from orpg.tools.rgbhex import RGBHex

from _object import *

from _circles import MapCircle
from _text import MapText
from _lines import MapLine
from _grid import GridLayer
from _fog import FogLayer

USE_BUFFER = True
if "wxMAC" in wx.PlatformInfo:
    USE_BUFFER = False

class MapCanvas(wx.ScrolledWindow):
    def __init__(self, parent, openrpg):
        wx.ScrolledWindow.__init__(self, parent, wx.ID_ANY, style=wx.HSCROLL | wx.VSCROLL | wx.NO_FULL_REPAINT_ON_RESIZE | wx.SUNKEN_BORDER)

        self.openrpg = openrpg
        self.log = self.openrpg.get_component("log")
        self.xml = self.openrpg.get_component("xml")
        self.dir_struct = self.openrpg.get_component("dir_struct")
        self.validate = self.openrpg.get_component("validate")
        self.settings = self.openrpg.get_component("settings")
        self.session = self.openrpg.get_component("session")
        self.chat = self.openrpg.get_component("chat")

        self.lock = Lock()

        self.RGBHex = RGBHex()

        self.toolWnd = parent

        self.shift = False
        self.ctrl = False

        self.selectedObjects = []
        self.overObjects = []
        self._objectId = 0

        self.gridLayer = GridLayer(self)
        self.circleLayer = MapCircle(self)
        self.textLayer = MapText(self)
        self.lineLayer = MapLine(self)
        self.fogLayer = FogLayer(self)

        self.zOrder = {}
        self.zOrder['tiles'] = []
        self.zOrder["back"] = []
        self.zOrder["front"] = []

        self.bgImage = None
        self.bgType = 'Image'
        self.bgPath = None
        self.backgroundColor = '#008040'

        self.gridType = 'Square'
        self.gridLines = wx.SOLID
        self.gridSnap = True
        self.gridSize = 60
        self.gridColor = "#000000"

        self.whiteboardColor = "#000000"

        self.zoomScale = 1.0
        self.lastZoomTime = time.time()
        self.lastZoomScale = 1.0

        self.useFog = False
        self.fogRegion = []
        self.fogColor = "#000000"

        self.zoomScale = 1.0
        self.lastZoomTime = time.time()
        self.lastZoomScale = 1.0
        self.zoomTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnZoomTimer, self.zoomTimer)
        #self.zoomTimer.Start(1000)

        self.imageCache = {}

        self._SetSize((1000,1000))

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnZoom)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_MOTION, self.OnMotion)
        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnBackground)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKey)
        self.Bind(wx.EVT_KEY_UP, self.OnKey)

        self.Bind(EVT_ENTER_OBJECT, self.EnterObject)
        self.Bind(EVT_LEAVE_OBJECT, self.LeaveObject)
        self.Bind(EVT_SELECT_OBJECT, self.ObjectSelected)
        self.Bind(EVT_DESELECT_OBJECT, self.ObjectDeselected)

        self.roleTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnRoleTimer, self.roleTimer)

        wx.CallAfter(self.OnSize, None)


    #Public API
    def UpdateMap(self, send=True):
        cdc = wx.ClientDC(self)
        self.PrepareDC(cdc)
        cdc.SetBackgroundMode(wx.TRANSPARENT)
        if USE_BUFFER:
            bdc = wx.BufferedDC(cdc, self._buffer)
            bdc.Clear()
            dc = wx.GraphicsContext.Create(bdc)
        else:
            cdc.Clear()
            dc = wx.GraphicsContext.Create(cdc)


        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.SetBrush(wx.TRANSPARENT_BRUSH)

        #Draw BG Color
        r,g,b = self.RGBHex.rgb_tuple(self.backgroundColor)
        brush = wx.Brush(wx.Color(r,g,b,255))
        dc.SetBrush(brush)

        path = dc.CreatePath()

        dc.PushState()
        path.AddRectangle(0, 0, self.size[0]+2, self.size[1]+2)
        dc.DrawPath(path)
        dc.PopState()

        dc.SetBrush(wx.NullBrush)

        #Set the Zoom
        dc.Scale(self.zoomScale, self.zoomScale)

        #Draw BG Image
        if self.bgImage != None:
            if self.bgType == 'Image':
                dc.DrawBitmap(self.bgImage, self.offset[0], self.offset[1], self.bgImage.GetWidth(), self.bgImage.GetHeight())
            else:
                bmpW = self.bgImage.GetWidth()
                bmpH = self.bgImage.GetHeight()

                pos = wx.Point(self.offset[0], self.offset[1])
                while pos.x < self.size[0]:
                    dc.DrawBitmap(self.bgImage, pos.x, pos.y, self.bgImage.GetWidth(), self.bgImage.GetHeight())
                    while pos.y < self.size[1]:
                        pos.y += bmpH
                        dc.DrawBitmap(self.bgImage, pos.x, pos.y, self.bgImage.GetWidth(), self.bgImage.GetHeight())
                    pos.y = 0
                    pos.x += bmpW

        #Draw Tiles
        for tile in self.zOrder['tiles']:
            tile.Draw(dc)

        #Draw Grid
        self.gridLayer.Draw(dc)

        #Draw Objects
        for object in self.zOrder['back']:
            object.Draw(dc)

        zl = self.zOrder.keys()
        zl.remove('back')
        zl.remove('front')
        zl.remove('tiles')
        zl.sort()

        for layer in zl:
            for object in self.zOrder[layer]:
                object.Draw(dc)

        for object in self.zOrder['front']:
            object.Draw(dc)


        #Draw Fog
        if self.useFog:
            self.fogLayer.Draw(dc)

        dc.SetBrush(wx.NullBrush)

        dc.Scale(1/self.zoomScale, 1/self.zoomScale)

        if self.zoomScale != 1.0:
            pos = self.GetViewStart()
            unit = self.GetScrollPixelsPerUnit()
            pos = [pos[0]*unit[0],pos[1]*unit[1]]
            font = wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
            dc.SetFont(font, wx.BLACK)

            dc.DrawText("Zoom Factor: " + str(self.zoomScale), pos[0], pos[1], dc.CreateBrush(wx.WHITE_BRUSH))

    def Clear(self):
        self._SetSize((1000,1000))
        self.selectedObjects = []
        self.overObjects = []
        self._objectId = 0
        self.bgImage = None
        self.bgType = 'Image'
        self.bgPath = None

        self.backgroundColor = '#008040'
        r, g, b = self.RGBHex.rgb_tuple(self.backgroundColor)
        self.toolWnd._SetColorBtn(wx.Color(r, g, b, 255), self.toolWnd.BGColorBtn)

        self.gridType = 'Square'
        self.gridLines = wx.SOLID
        self.gridSnap = True
        self.gridSize = 60
        self.gridColor = "#000000"

        self.whiteboardColor = "#000000"
        r, g, b = self.RGBHex.rgb_tuple(self.whiteboardColor)
        self.toolWnd._SetColorBtn(wx.Color(r, g, b, 255), self.toolWnd.ColorBtn)

        self.zoomScale = 1.0
        self.lastZoomTime = time.time()
        self.lastZoomScale = 1.0

        self.useFog = False
        self.fogRegion = []
        self.fogColor = "#000000"

        self.OnRemoveAllObjects(None)

        self.toolWnd.Freeze()
        for btn in self.toolWnd.exclusiveToolList:
            self.toolWnd.exclusiveToolList[btn].SetToggled(False)

        self.toolWnd.FogBtn.SetToggled(False)
        self.toolWnd.SelectorBtn.SetToggled(True)
        self.toolWnd.Thaw()

    def GetNewObjectId(self):
        return str(self._objectId+1)

    #Map Events
    def OnBackground(self, event):
        #Dont do it
        pass

    def OnPaint(self, event):
        if USE_BUFFER:
            dc = wx.PaintDC(self)
            self.PrepareDC(dc)
            dc.DrawBitmap(self._buffer, 0, 0)
        else:
            event.Skip()


    def OnSize(self, event):
        self._buffer = wx.EmptyBitmap(self.size[0], self.size[1])
        self._FixScroll()
        wx.CallAfter(self.UpdateMap)


    def OnZoom(self, event):
        if event.GetWheelRotation() < 0:
            self.zoomScale -= .1
            if self.zoomScale < .5:
                self.zoomScale = .5
            else:
                self.lastZoomTime = time.time()
                self._FixScroll()
                self.UpdateMap()
        else:
            self.zoomScale += .1

            if self.zoomScale > 1.5:
                self.zoomScale = 1.5
            else:
                self.lastZoomTime = time.time()
                self._FixScroll()
                self.UpdateMap()

    def OnKey(self, event):
        self.shift = False
        self.ctrl = False
        if event.ShiftDown():
            self.shift = True
        elif event.ControlDown():
            self.ctrl = True


    def EnterObject(self, event):
        obj = event.GetObject()
        self.overObjects.append(obj)
        obj.Highlight()

    def LeaveObject(self, event):
        obj = event.GetObject()
        try:
            self.overObjects.remove(obj)
        except:
            pass
        obj.UnHighlight()

    def ObjectSelected(self, event):
        obj = event.GetObject()
        self.selectedObjects.append(obj)
        try:
            self.overObjects.remove(obj)
        except:
            pass
        obj.UnHighlight()

    def ObjectDeselected(self, event):
        obj = event.GetObject()
        try:
            self.selectedObjects.remove(obj)
        except:
            pass
        obj.Update()

    def OnLeftDown(self, event):
        dc = wx.ClientDC(self)
        self.PrepareDC(dc)
        pos = event.GetLogicalPosition(dc)
        pos.x /= self.zoomScale
        pos.y /= self.zoomScale

        if self.toolWnd.AddShapeBtn.GetToggled() and self.toolWnd.currentShape == 'Circle':
            self.circleLayer.OnLeftDown(pos)

        elif self.toolWnd.AddTextBtn.GetToggled():
            self.textLayer.OnLeftDown(pos)

        elif self.toolWnd.DrawBtn.GetToggled():
            self.lineLayer.OnLeftDown(pos)

        elif self.toolWnd.SelectorBtn.GetToggled() and (self.selectedObjects == [] or self.ctrl or self.shift) and not (self.useFog and self.fogLayer.region.Contains(pos.x, pos.y) and not self.toolWnd.gmToolBar.IsShown()):
            self.initiatPos = pos
            self.lxd = 0
            self.lyd = 0
            if len(self.overObjects) == 0:
                return
            elif len(self.overObjects) == 1:
                self.overObjects[0].Select()
            else:
                if not self.shift:
                    menu = wx.Menu("Object Selection")
                    id = 0
                    for obj in self.overObjects:
                        menu.Append(id, obj.GetName())
                        id += 1

                    def selectmenu(event):
                        id = event.GetId()
                        self.overObjects[id].Select()
                        self.Unbind(wx.EVT_MENU)

                    self.Bind(wx.EVT_MENU, selectmenu)
                    self.PopupMenu(menu)
                else:
                    for i in xrange(len(self.overObjects)):
                        self.overObjects[0].Select()

        elif self.toolWnd.SelectorBtn.GetToggled() and not self.selectedObjects == []:
            xd = (self.initiatPos.x+pos.x)*(self.initiatPos.x+pos.x)
            yd = (self.initiatPos.y+pos.y)*(self.initiatPos.y+pos.y)

            for i in xrange(len(self.selectedObjects)):
                self.selectedObjects[0].Deselect()

        elif self.toolWnd.FogToolBtn.GetToggled():
            self.fogLayer.OnLeftDown(pos)

    def OnLeftDClick(self, event):
        dc = wx.ClientDC(self)
        self.PrepareDC(dc)
        pos = event.GetLogicalPosition(dc)
        pos.x /= self.zoomScale
        pos.y /= self.zoomScale

        if self.toolWnd.DrawBtn.GetToggled():
            self.lineLayer.OnLeftDClick(pos)

    def OnLeftUp(self, event):
        dc = wx.ClientDC(self)
        self.PrepareDC(dc)
        pos = event.GetLogicalPosition(dc)
        pos.x /= self.zoomScale
        pos.y /= self.zoomScale

        if self.toolWnd.AddShapeBtn.GetToggled() and self.toolWnd.currentShape == 'Circle':
            self.circleLayer.OnLeftUp(pos)

        elif self.toolWnd.FogToolBtn.GetToggled():
            self.fogLayer.OnLeftUp(pos)

        elif self.toolWnd.DrawBtn.GetToggled():
            self.lineLayer.OnLeftUp(pos)

        elif self.toolWnd.SelectorBtn.GetToggled() and self.selectedObjects == []:
            rgn = wx.Region(self.initiatPos.x, self.initiatPos.y, self.lxd, self.lyd)

            for object in self.zOrder['back']:
                if rgn.Contains(object.start.x, object.start.y):
                    object.Select()

            zl = self.zOrder.keys()
            zl.remove('back')
            zl.remove('front')
            zl.remove('tiles')
            zl.sort()

            for layer in zl:
                for object in self.zOrder[layer]:
                    if rgn.Contains(object.start.x, object.start.y):
                        object.Select()

            for object in self.zOrder['front']:
                if rgn.Contains(object.start.x, object.start.y):
                    object.Select()

            self.lxd = 0
            self.lyd = 0
            self.initiatPos = pos
        self.Refresh()

    def OnMotion(self, event):
        dc = wx.ClientDC(self)
        self.PrepareDC(dc)
        pos = event.GetLogicalPosition(dc)
        pos.x /= self.zoomScale
        pos.y /= self.zoomScale


        #HitTest
        for object in self.zOrder['back']:
            object.HitTest(pos)

        zl = self.zOrder.keys()
        zl.remove('back')
        zl.remove('front')
        zl.remove('tiles')
        zl.sort()

        for layer in zl:
            for object in self.zOrder[layer]:
                object.HitTest(pos)

        for object in self.zOrder['front']:
            object.HitTest(pos)

        if self.toolWnd.AddShapeBtn.GetToggled() and event.m_leftDown and self.toolWnd.currentShape == 'Circle':
            self.circleLayer.OnMotion(pos)

        elif self.toolWnd.DrawBtn.GetToggled() and self.lineLayer.start != wx.Point(0,0):
            self.lineLayer.OnMotion(pos)

        elif self.toolWnd.SelectorBtn.GetToggled() and self.selectedObjects != [] and not (self.ctrl or self.shift):
            xd = (pos.x-self.initiatPos.x)
            yd = (pos.y-self.initiatPos.y)
            for obj in self.selectedObjects:
                obj.start.x += xd
                obj.start.y += yd
                obj.Update()
                self.initiatPos = pos


        elif self.toolWnd.SelectorBtn.GetToggled() and self.selectedObjects == [] and event.m_leftDown:
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            pen = wx.Pen(wx.BLACK, 3, wx.DOT)
            dc.SetPen(pen)
            dc.SetLogicalFunction(wx.INVERT)

            xd = (pos.x-self.initiatPos.x)
            yd = (pos.y-self.initiatPos.y)

            if self.lxd != 0 and self.lyd != 0:
                r = wx.Rect(self.initiatPos.x, self.initiatPos.y, self.lxd, self.lyd)
                dc.DrawRectangleRect(r)

            self.lxd = xd
            self.lyd = yd
            r = wx.Rect(self.initiatPos.x, self.initiatPos.y, self.lxd, self.lyd)
            dc.DrawRectangleRect(r)

        elif (self.toolWnd.FogToolBtn.GetToggled()) and event.m_leftDown:
            self.fogLayer.OnMotion(pos)

    def OnRightDown(self, event):
        mapmenu = wx.Menu()

        item = wx.MenuItem(mapmenu, wx.ID_ANY, "Load Map", "Load Map")
        #self.Bind(wx.EVT_MENU, self.OnOpenBtn, item)
        mapmenu.AppendItem(item)

        item = wx.MenuItem(mapmenu, wx.ID_ANY, "Save Map", "Save Map")
        #self.Bind(wx.EVT_MENU, self.OnSaveBtn, item)
        mapmenu.AppendItem(item)

        item = wx.MenuItem(mapmenu, wx.ID_ANY, "Default Map", "Default Map")
        self.Bind(wx.EVT_MENU, self.OnDefaultBtn, item)
        mapmenu.AppendItem(item)

        item = wx.MenuItem(mapmenu, wx.ID_ANY, "Map Properties", "Map Properties")
        #self.Bind(wx.EVT_MENU, OnMapPropsBtn, item)
        mapmenu.AppendItem(item)

        bgmenu = wx.Menu()

        item = wx.MenuItem(bgmenu, wx.ID_ANY, "Change Background Image", "Change Background Image")
        self.Bind(wx.EVT_MENU, self.OnBGBtn, item)
        bgmenu.AppendItem(item)

        item = wx.MenuItem(bgmenu, wx.ID_ANY, "Change Background Color", "Change Background Color")
        self.Bind(wx.EVT_MENU, self.OnBGColorBtn, item)
        bgmenu.AppendItem(item)

        item = wx.MenuItem(bgmenu, wx.ID_ANY, "Grid Properties", "Grid Properties")
        #self.Bind(wx.EVT_MENU, self.OnGridBtn, item)
        bgmenu.AppendItem(item)

        fogmenu = wx.Menu()

        item = wx.MenuItem(fogmenu, wx.ID_ANY, "Toggle Fog", "Toggle Fog")
        self.Bind(wx.EVT_MENU, self.OnFogBtn, item)
        fogmenu.AppendItem(item)

        item = wx.MenuItem(fogmenu, wx.ID_ANY, "Fog Color", "Fog Color")
        self.Bind(wx.EVT_MENU, self.OnFogColorBtn, item)
        fogmenu.AppendItem(item)

        menu = wx.Menu()

        if self.toolWnd.gmToolBar.IsShown():
            menu.AppendMenu(wx.ID_ANY, "Map", mapmenu)
            menu.AppendMenu(wx.ID_ANY, "Background", bgmenu)
            menu.AppendMenu(wx.ID_ANY, "Fog", fogmenu)
            menu.AppendSeparator()
            item = wx.MenuItem(menu, wx.ID_ANY, "Miniture Properties", "Miniture Properties")
            #self.Bind(wx.EVT_MENU, self.OnColorBtn, item)
            menu.AppendItem(item)
            menu.AppendSeparator()

        item = wx.MenuItem(menu, wx.ID_ANY, "Whiteboard Color", "Whiteboard Color")
        self.Bind(wx.EVT_MENU, self.OnColorBtn, item)
        menu.AppendItem(item)


        def ObjectMenu(event):
            id = event.GetId()
            objid = int(menu.GetHelpString(id))
            menuname = menu.GetLabel(id)
            obj = self.overObjects[objid]

            if menuname == "Move To Back":
                self.MoveToBack(obj)

            elif menuname == "Move Back":
                self.MoveBack(obj)

            elif menuname == "Move Forward":
                self.MoveForward(obj)

            elif menuname == "Move To Front":
                self.MoveToFront(obj)

            elif menuname == "Remove":
                self.zOrder[obj.zOrder].remove(obj)
                obj.Update()

            self.Unbind(wx.EVT_MENU)
            self.overObjects.remove(obj)


        if len(self.overObjects):
            menu.AppendSeparator()

        id = 0
        for obj in self.overObjects:
            if obj.IsShown() or self.toolWnd.gmToolBar.IsShown():
                objmenu = wx.Menu()
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Move To Back", str(id))
                self.Bind(wx.EVT_MENU, ObjectMenu, item)
                objmenu.AppendItem(item)
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Move Back", str(id))
                self.Bind(wx.EVT_MENU, ObjectMenu, item)
                objmenu.AppendItem(item)
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Move Forward", str(id))
                self.Bind(wx.EVT_MENU, ObjectMenu, item)
                objmenu.AppendItem(item)
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Move To Front", str(id))
                self.Bind(wx.EVT_MENU, ObjectMenu, item)
                objmenu.AppendItem(item)
                objmenu.AppendSeparator()
                if obj.IsShown():
                    item = wx.MenuItem(objmenu, wx.ID_ANY, "Hide", str(id))
                    self.Bind(wx.EVT_MENU, obj.Hide, item)
                    objmenu.AppendItem(item)
                    objmenu.AppendSeparator()
                elif self.toolWnd.gmToolBar.IsShown():
                    item = wx.MenuItem(objmenu, wx.ID_ANY, "Show", str(id))
                    self.Bind(wx.EVT_MENU, obj.Show, item)
                    objmenu.AppendItem(item)
                    objmenu.AppendSeparator()
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Remove", str(id))
                self.Bind(wx.EVT_MENU, ObjectMenu, item)
                objmenu.AppendItem(item)
                item = wx.MenuItem(objmenu, wx.ID_ANY, "Properties", str(id))
                self.Bind(wx.EVT_MENU, obj.ShowProperties, item)
                objmenu.AppendItem(item)
                menu.AppendMenu(wx.ID_ANY, obj.GetName(), objmenu)

        menu.AppendSeparator()
        item = wx.MenuItem(menu, wx.ID_ANY, "Remove All Objects", "Remove All Whiteboard Items")
        self.Bind(wx.EVT_MENU, self.OnRemoveAllObjects, item)
        menu.AppendItem(item)

        self.PopupMenu(menu)


    def OnRemoveAllObjects(self, event):
        for layer in self.zOrder:
            for i in xrange(len(self.zOrder[layer])):
                del self.zOrder[layer][0]

        self.zOrder = {}
        self.zOrder['tiles'] = []
        self.zOrder["back"] = []
        self.zOrder["front"] = []
        if event != None:
            self.UpdateMap()

    def MoveToBack(self, object):
        self.zOrder[object.zOrder].remove(object)
        self.zOrder['back'].append(object)
        object.zOrder = 'back'
        self.UpdateMap()

    def MoveToFront(self, object):
        self.zOrder[object.zOrder].remove(object)
        self.zOrder['front'].append(object)
        object.zOrder = 'front'
        self.UpdateMap()

    def MoveBack(self, object):
        self.zOrder[object.zOrder].remove(object)

        zl = self.zOrder.keys()
        zl.remove('back')
        zl.remove('front')
        zl.remove('tiles')
        zl.sort()
        lzo = 1
        if len(zl):
            lzo = zl.pop()

        if object.zOrder == 'back' or object.zOrder == 1:
            self.zOrder['back'].append(object)
            object.zOrder = 'back'
        elif object.zOrder == 'front':
            if not self.zOrder.has_key(lzo):
                self.zOrder[lzo] = []
            self.zOrder[lzo].append(object)
            object.zOrder = lzo
        else:
            object.zOrder -= 1
            if not self.zOrder.has_key(object.zOrder):
                self.zOrder[object.zOrder] = []
            self.zOrder[object.zOrder].append(object)
        self.UpdateMap()

    def MoveForward(self, object):
        self.zOrder[object.zOrder].remove(object)

        zl = self.zOrder.keys()
        zl.remove('back')
        zl.remove('front')
        zl.remove('tiles')
        zl.sort()
        lzo = 1
        if len(zl):
            lzo = zl.pop()

        if object.zOrder == 'back':
            if not self.zOrder.has_key(1):
                self.zOrder[1] = []
            self.zOrder[1].append(object)
            object.zOrder = 1
        elif z == 'front':
            self.zOrder['front'].append(object)
            object.zOrder = 'front'
        else:
            object.zOrder += 1
            if not self.zOrder.has_key(object.zOrder):
                self.zOrder[object.zOrder] = []
            self.zOrder[object.zOrder].append(object)
        self.UpdateMap()

    def OnScroll(self, event):
        event.Skip()
        self.Refresh()

    def OnZoomTimer(self, event):
        if (time.time() - self.lastZoomTime) >= 3 and self.lastZoomScale != self.zoomScale:
            #Send Zoome Notice to other clients
            self.lastZoomTime = time.time()
            self.lastZoomScale = self.zoomScale

    def OnRoleTimer(self, event):
        #Figure out the users role
        if self.session.my_role() == self.session.ROLE_GM:
            self.role = 'GM'
        elif self.session.my_role() == self.session.ROLE_PLAYER:
            self.role = 'Player'
        else:
            self.role = 'Lurker'

        if self.role == 'GM' and not self.toolWnd.gmToolBar.IsShown() and not (str(self.session.group_id) == '0' and str(self.session.status) == '1'):
            self.toolWnd.Freeze()
            self.toolWnd.gmToolBar.Show()
            self.toolWnd.Thaw()
        elif self.role == 'Player' and not (str(self.session.group_id) == '0' and str(self.session.status) == '1'):
            if self.toolWnd.gmToolBar.IsShown():
                self.toolWnd.Freeze()
                self.toolWnd.gmToolBar.Hide()
                self.toolWnd.Thaw()

            if not self.toolWnd.playerToolBar.IsShown():
                self.toolWnd.Freeze()
                self.toolWnd.playerToolBar.Show()
                self.toolWnd.Thaw()
        elif self.role == 'Lurker' or (str(self.session.group_id) == '0' and str(self.session.status) == '1'):
            if self.toolWnd.playerToolBar.IsShown():
                self.toolWnd.Freeze()
                self.toolWnd.gmToolBar.Hide()
                self.toolWnd.playerToolBar.Hide()
                self.toolWnd.Thaw()

        try:
            self.toolWnd.Layout()
        except:
            pass

    def OnClose(self, event):
        self.zoomTimer.Stop()
        self.roleTimer.Stop()
        event.Skip()

    #Toolbar Events
    def OnDefaultBtn(self, event):
        self.Clear()
        wx.CallAfter(self.UpdateMap)

    def OnColorBtn(self, event):
        newcolor = self.RGBHex.do_hex_color_dlg(self.toolWnd)
        if newcolor == None:
            return

        self.whiteboardColor = newcolor
        r, g, b = self.RGBHex.rgb_tuple(self.whiteboardColor)
        self.toolWnd._SetColorBtn(wx.Color(r, g, b, 255), self.toolWnd.ColorBtn)

    def OnBGColorBtn(self, event):
        newcolor = self.RGBHex.do_hex_color_dlg(self.toolWnd)
        if newcolor == None:
            return

        self.backgroundColor = newcolor
        r, g, b = self.RGBHex.rgb_tuple(self.backgroundColor)
        self.toolWnd._SetColorBtn(wx.Color(r, g, b, 255), self.toolWnd.BGColorBtn)
        self.UpdateMap()

    def OnFogColorBtn(self, event):
        newcolor = self.RGBHex.do_hex_color_dlg(self.toolWnd)
        if newcolor == None:
            return

        self.fogColor = newcolor
        r, g, b = self.RGBHex.rgb_tuple(self.fogColor)
        self.toolWnd._SetColorBtn(wx.Color(r, g, b, 255), self.toolWnd.FogColorBtn)
        self.UpdateMap()

    def OnExlusiveBtn(self, event):
        id = event.GetId()
        #This is backwards because the Toggle Switch does not get set until AFTER The mouse gets released
        if not self.toolWnd.exclusiveToolList[id].GetToggled():
            self.toolWnd.Freeze()
            #Disable all mutualy exclusive tools
            for btn in self.toolWnd.exclusiveToolList:
                if self.toolWnd.exclusiveToolList[btn].GetId() != id:
                    self.toolWnd.exclusiveToolList[btn].SetToggled(False)
            self.toolWnd.Thaw()
        else:
            wx.CallAfter(self.toolWnd.SelectorBtn.SetToggled, True)

    def OnFogBtn(self, event):
        if not self.toolWnd.FogBtn.GetToggled():
            self.useFog = True
        else:
            self.useFog = False
            self.toolWnd.Freeze()
            self.toolWnd.SelectorBtn.SetToggled(True)
            self.toolWnd.FogToolBtn.SetToggled(False)
            self.toolWnd.Thaw()
        self.fogRegion = []
        self.UpdateMap()

    def OnBGBtn(self, event):
        dlg = wx.Dialog(self.toolWnd, wx.ID_ANY, title="Background Properties")
        sizer = wx.BoxSizer(wx.HORIZONTAL)

        filename = wx.TextCtrl(dlg, wx.ID_ANY)
        filename.Hide()

        bgpath = wx.TextCtrl(dlg, wx.ID_ANY)
        if self.bgPath != None:
            bgpath.SetValue(self.bgPath)

        bgtype = wx.Choice(dlg, wx.ID_ANY, choices=['Image', 'Texture'])
        bgtype.SetStringSelection(self.bgType)

        browsebtn = wx.Button(dlg, wx.ID_ANY, "Browse")
        okbtn = wx.Button(dlg, wx.ID_OK)
        cancelbtn = wx.Button(dlg, wx.ID_CANCEL)

        sizer.Add(wx.StaticText(dlg, wx.ID_ANY, "Image Path"), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 2)
        sizer.Add(bgpath, 0, wx.EXPAND|wx.ALL, 3)
        sizer.Add(wx.StaticText(dlg, wx.ID_ANY, "Image Type"), 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 2)
        sizer.Add(bgtype, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 3)
        sizer.Add(browsebtn, 0, wx.EXPAND|wx.ALL, 2)
        sizer.Add(okbtn, 0, wx.EXPAND|wx.ALL, 3)
        sizer.Add(cancelbtn, 0, wx.EXPAND|wx.ALL, 2)

        dlg.SetSizer(sizer)
        dlg.SetAutoLayout(True)
        dlg.Fit()

        def OnBrowse(event):
            filedlg = wx.FileDialog(self, "Select an Image File", self.dir_struct["user"], wildcard="Image files (*.bmp, *.gif, *.jpg, *.png)|*.bmp;*.gif;*.jpg;*.png", style=wx.HIDE_READONLY|wx.OPEN)
            if filedlg.ShowModal() != wx.ID_OK:
                filedlg.Destroy()
                return

            bgpath.SetValue(filedlg.GetPath())
            filename.SetValue(filedlg.GetFilename())

        dlg.Bind(wx.EVT_BUTTON, OnBrowse, browsebtn)
        dlg.Show()

        if not dlg.ShowModal() == wx.ID_OK:
            dlg.Destroy()
            return

        self.bgType = bgtype.GetStringSelection()

        if bgpath.GetValue().lower().find('http:') == -1:
            file = open(bgpath.GetValue(), "rb")
            imgdata = file.read()
            file.close()

            (imgtype,j) = mimetypes.guess_type(filename.GetValue())

            postdata = urllib.urlencode({'filename':filename.GetValue(), 'imgdata':imgdata, 'imgtype':imgtype})

            thread.start_new_thread(self.__Upload, (postdata, bgpath.GetValue(), "Background"))
        else:
            self.bgImage = self._LoadImage(bgpath.GetValue())
            self.UpdateMap()


    #Private Methods
    def _SetSize(self, size):
        if size[0] == -1:
            size[0] = self.size[0]
        if size[1] == -1:
            size[1] = self.size[1]

        if size[0] < 300:
            size = (300, size[1])
        if size[1] < 300:
            size = (size[0], 300)

        size1  = self.GetClientSizeTuple()

        if size[0] < size1[0]:
            size = (size1[0], size[1])
        if size[1] < size1[1]:
            size = (size[0], size1[1])

        self.sizeChanged = 1
        self.size = size
        self._FixScroll()

    def _FixScroll(self):
        scale = self.zoomScale
        pos = self.GetViewStart()
        unit = self.GetScrollPixelsPerUnit()
        pos = [pos[0]*unit[0],pos[1]*unit[1]]
        size = self.GetClientSize()
        unit = [10*scale,10*scale]
        if (unit[0] == 0 or unit[1] == 0):
            return
        pos[0] /= unit[0]
        pos[1] /= unit[1]
        mx = [int(self.size[0]*scale/unit[0])+1, int(self.size[1]*scale/unit[1]+1)]
        self.SetScrollbars(unit[0], unit[1], mx[0], mx[1], pos[0], pos[1])

    def _LoadImage(self, path, miniId=None):
        if self.imageCache.has_key(path):
            return self.imageCache[path]

        while len(self.imageCache) > int(self.settings.get_setting("ImageCacheSize")):
            keys = self.imageCache.keys()
            del self.imageCache[keys[0]]


        thread.start_new_thread(self.__DownloadImage, (path, miniId))

        return wx.Bitmap(orpg.dirpath.dir_struct["icon"] + "fetching.png", wx.BITMAP_TYPE_PNG)

    def _ClearCache(self):
        for key in self.imageCache:
            del self.imageCache[key]

    #Threads
    def __Upload(self, postdata, filename, type="Background"):
        self.lock.acquire()

        url = self.settings.get_setting('ImageServerBaseURL')
        file = urllib.urlopen(url, postdata)
        recvdata = file.read()
        file.close()
        try:
            xml_dom = minidom.parseString(recvdata)._get_documentElement()

            if xml_dom.nodeName == 'path':
                path = xml_dom.getAttribute('url')
                path = urllib.unquote(path)

                if type == 'Background':
                    self.bgImage = self._LoadImage(path)
                    self.bgPath = path

                else:
                    self.minis.append(self.mapLayer.AddMiniture(path))

                self.UpdateMap()

            else:
                self.chat.InfoPost(xml_dom.getAttribute('msg'))
        except Exception, e:
            print e
            print recvdata

        self.lock.release()

    def __DownloadImage(self, path, miniId):
        self.lock.acquire()

        uriPath = urllib.unquote(path)
        try:
            data = urllib.urlretrieve(uriPath)

            if data[0] and data[1].getmaintype() == "image":
                imageType = data[1].gettype()
                img = wx.ImageFromMime(data[0], imageType).ConvertToBitmap()
                self.imageCache[path] = img

                if miniId == None:
                    self.bgImage = img
                    if self.bgType == 'Image':
                        self._SetSize((img.GetHeight(), img.GetWidth()))

                else:
                    mini = self.GetMiniById(miniId)
                    mini.image = img

                self.UpdateMap()
        except Exception, e:
            self.chat.InfoPost("Unable to resolve/open the specified URI; image was NOT laoded:" + path)

        urllib.urlcleanup()
        self.lock.release()