view orpg/mapper/miniatures.py @ 65:4840657c23c5 ornery-dev

Traipse Dev 'OpenRPG' {090817-02} 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: Update to Settings Menu. When user changes font or font size the chat window will now update without needing a restart. Change to dirpath/dir_struct file. No longer creates OPENRPG_BASE environment key.
author sirebral
date Mon, 17 Aug 2009 06:56:31 -0500
parents fdd70f11bc7e
children 449a8900f9ac
line wrap: on
line source

# Copyright (C) 2000-2001 The OpenRPG Project
#
#    openrpg-dev@lists.sourceforge.net
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# --
#
# File: mapper/miniatures.py
# Author: Chris Davis
# Maintainer:
# Version:
#   $Id: miniatures.py,v 1.46 2007/12/07 20:39:50 digitalxero Exp $
#
# Description: This file contains some of the basic definitions for the chat
# utilities in the orpg project.
#
__version__ = "$Id: miniatures.py,v 1.46 2007/12/07 20:39:50 digitalxero Exp $"

from base import *
import thread
import time
import urllib
import os.path

MIN_STICKY_BACK = -0XFFFFFF
MIN_STICKY_FRONT = 0xFFFFFF

##----------------------------------------
##  miniature object
##----------------------------------------

FACE_NONE = 0
FACE_NORTH = 1
FACE_NORTHEAST = 2
FACE_EAST = 3
FACE_SOUTHEAST = 4
FACE_SOUTH = 5
FACE_SOUTHWEST = 6
FACE_WEST = 7
FACE_NORTHWEST = 8
SNAPTO_ALIGN_CENTER = 0
SNAPTO_ALIGN_TL = 1

def cmp_zorder(first,second):
    f = first.zorder
    s = second.zorder
    if f == None: f = 0
    if s == None: s = 0
    if f == s: value = 0
    elif f < s: value = -1
    else: value = 1
    return value

class BmpMiniature:
    def __init__(self, id,path, bmp, pos=cmpPoint(0,0), heading=FACE_NONE, face=FACE_NONE, label="", locked=False, hide=False, snap_to_align=SNAPTO_ALIGN_CENTER, zorder=0, width=0, height=0, log=None, local=False, localPath='', localTime=-1):
        self.log = log
        self.log.log("Enter BmpMiniature", ORPG_DEBUG)
        self.heading = heading
        self.face = face
        self.label = label
        self.path = path
        self.bmp = bmp
        self.pos = pos
        self.selected = False
        self.locked = locked
        self.snap_to_align = snap_to_align
        self.hide = hide
        self.id = id
        self.zorder = zorder
        self.left = 0
        self.local = local
        self.localPath = localPath
        self.localTime = localTime
        if not width: self.width = 0
        else: self.width = width
        if not height: self.height = 0
        else: self.height = height
        self.right = bmp.GetWidth()
        self.top = 0
        self.bottom = bmp.GetHeight()
        self.isUpdated = False
        self.gray = False
        self.log.log("Exit BmpMiniature", ORPG_DEBUG)

    def __del__(self):
        self.log.log("Enter BmpMiniature->__del__(self)", ORPG_DEBUG)
        del self.bmp
        self.bmp = None
        self.log.log("Exit BmpMiniature->__del__(self)", ORPG_DEBUG)

    def set_bmp(self, bmp):
        self.log.log("Enter BmpMiniature->set_bmp(self, bmp)", ORPG_DEBUG)
        self.bmp = bmp
        self.log.log("Exit BmpMiniature->set_bmp(self, bmp)", ORPG_DEBUG)

    def set_min_props(self, heading=FACE_NONE, face=FACE_NONE, label="", locked=False, hide=False, width=0, height=0):
        self.log.log("Enter BmpMiniature->set_min_props(self, heading, face, label, locked, hide, width, height)", 
            ORPG_DEBUG)
        self.heading = heading
        self.face = face
        self.label = label
        if locked: self.locked = True
        else: self.locked = False
        if hide: self.hide = True
        else: self.hide = False
        self.width = int(width)
        self.height = int(height)
        self.isUpdated = True
        self.log.log("Exit BmpMiniature->set_min_props(self, heading, face, label, locked, hide, width, height)", 
            ORPG_DEBUG)

    def hit_test(self, pt):
        self.log.log("Enter BmpMiniature->hit_test(self, pt)", ORPG_DEBUG)
        rect = self.get_rect()
        result = None
        result = rect.InsideXY(pt.x, pt.y)
        self.log.log("Exit BmpMiniature->hit_test(self, pt)", ORPG_DEBUG)
        return result

    def get_rect(self):
        self.log.log("Enter BmpMiniature->get_rect(self)", ORPG_DEBUG)
        ret = wx.Rect(self.pos.x, self.pos.y, self.bmp.GetWidth(), self.bmp.GetHeight())
        self.log.log("Exit BmpMiniature->get_rect(self)", ORPG_DEBUG)
        return ret

    def draw(self, dc, mini_layer, op=wx.COPY):
        self.log.log("Enter BmpMiniature->draw(self, dc, mini_layer, op)", ORPG_DEBUG)
        if isinstance(self.bmp, tuple):
            self.log.log("bmp is a tuple, it shouldnt be!", ORPG_INFO)
            self.bmp = wx.ImageFromMime(self.bmp[1], self.bmp[2]).ConvertToBitmap()
        if self.bmp != None and self.bmp.Ok():
            # check if hidden and GM: we outline the mini in grey (little bit smaller than the actual size)
            # and write the label in the center of the mini
            if self.hide and mini_layer.canvas.frame.session.my_role() == mini_layer.canvas.frame.session.ROLE_GM:
                self.log.log("Enter BmpMiniature->draw->Draw Hidden", ORPG_DEBUG)
                # set the width and height of the image
                if self.width and self.height:
                    tmp_image = self.bmp.ConvertToImage()
                    tmp_image.Rescale(int(self.width), int(self.height))
                    tmp_image.ConvertAlphaToMask()
                    self.bmp = tmp_image.ConvertToBitmap()
                    mask = wx.Mask(self.bmp, wx.Colour(tmp_image.GetMaskRed(), 
                        tmp_image.GetMaskGreen(), tmp_image.GetMaskBlue()))
                    self.bmp.SetMask(mask)
                    del tmp_image
                    del mask
                self.left = 0
                self.right = self.bmp.GetWidth()
                self.top = 0
                self.bottom = self.bmp.GetHeight()
                # grey outline
                graypen = wx.Pen("gray", 1, wx.DOT)
                dc.SetPen(graypen)
                dc.SetBrush(wx.TRANSPARENT_BRUSH)
                #if width or height < 20 then offset = 1
                if self.bmp.GetWidth() <= 20: xoffset = 1
                else: xoffset = 5
                if self.bmp.GetHeight() <= 20: yoffset = 1
                else: yoffset = 5
                dc.DrawRectangle(self.pos.x + xoffset, 
                    self.pos.y + yoffset, self.bmp.GetWidth() - (xoffset * 2), 
                    self.bmp.GetHeight() - (yoffset * 2))
                dc.SetBrush(wx.NullBrush)
                dc.SetPen(wx.NullPen)
                ## draw label in the center of the mini
                label = mini_layer.get_mini_label(self)
                if len(label):
                    dc.SetTextForeground(wx.RED)
                    (textWidth,textHeight) = dc.GetTextExtent(label)
                    x = self.pos.x +((self.bmp.GetWidth() - textWidth) /2) - 1
                    y = self.pos.y + (self.bmp.GetHeight() / 2)
                    dc.SetPen(wx.GREY_PEN)
                    dc.SetBrush(wx.LIGHT_GREY_BRUSH)
                    dc.DrawRectangle(x, y, textWidth+2, textHeight+2)
                    if (textWidth+2 > self.right):
                        self.right += int((textWidth+2-self.right)/2)+1
                        self.left -= int((textWidth+2-self.right)/2)+1
                    self.bottom = y+textHeight+2-self.pos.y
                    dc.SetPen(wx.NullPen)
                    dc.SetBrush(wx.NullBrush)
                    dc.DrawText(label, x+1, y+1)

                #selected outline
                if self.selected:
                    dc.SetPen(wx.RED_PEN)
                    dc.SetBrush(wx.TRANSPARENT_BRUSH)
                    dc.DrawRectangle(self.pos.x, self.pos.y, self.bmp.GetWidth(), self.bmp.GetHeight())
                    dc.SetBrush(wx.NullBrush)
                    dc.SetPen(wx.NullPen)
                self.log.log("Exit BmpMiniature->draw->Draw Hidden", ORPG_DEBUG)
                return True
            elif self.hide: self.log.log("Enter/Exit BmpMiniature->draw->Skip Hidden", ORPG_DEBUG); return True

            else:
                self.log.log("Enter BmpMiniature->draw->Not Hidden", ORPG_DEBUG)
                # set the width and height of the image
                bmp = self.bmp
                if self.width and self.height:
                    tmp_image = self.bmp.ConvertToImage()
                    tmp_image.Rescale(int(self.width), int(self.height))
                    tmp_image.ConvertAlphaToMask()
                    self.bmp = tmp_image.ConvertToBitmap()
                    mask = wx.Mask(self.bmp, wx.Colour(tmp_image.GetMaskRed(), 
                        tmp_image.GetMaskGreen(), tmp_image.GetMaskBlue()))
                    self.bmp.SetMask(mask)
                    if self.gray:
                        tmp_image = tmp_image.ConvertToGreyscale()
                        bmp = tmp_image.ConvertToBitmap()
                    else: bmp = self.bmp
                dc.DrawBitmap(bmp, self.pos.x, self.pos.y, True)
                self.left = 0
                self.right = self.bmp.GetWidth()
                self.top = 0
                self.bottom = self.bmp.GetHeight()

                # Draw the facing marker if needed
                if self.face != 0:
                    x_mid = self.pos.x + (self.bmp.GetWidth()/2)
                    x_right = self.pos.x + self.bmp.GetWidth()
                    y_mid = self.pos.y + (self.bmp.GetHeight()/2)
                    y_bottom = self.pos.y + self.bmp.GetHeight()
                    dc.SetPen(wx.WHITE_PEN)
                    dc.SetBrush(wx.RED_BRUSH)
                    triangle = []

                    # Figure out which direction to draw the marker!!
                    if self.face == FACE_WEST:
                        triangle.append(cmpPoint(self.pos.x,self.pos.y))
                        triangle.append(cmpPoint(self.pos.x - 5, y_mid))
                        triangle.append(cmpPoint(self.pos.x, y_bottom))
                    elif self.face ==  FACE_EAST:
                        triangle.append(cmpPoint(x_right, self.pos.y))
                        triangle.append(cmpPoint(x_right + 5, y_mid))
                        triangle.append(cmpPoint(x_right, y_bottom))
                    elif self.face ==  FACE_SOUTH:
                        triangle.append(cmpPoint(self.pos.x, y_bottom))
                        triangle.append(cmpPoint(x_mid, y_bottom + 5))
                        triangle.append(cmpPoint(x_right, y_bottom))
                    elif self.face ==  FACE_NORTH:
                        triangle.append(cmpPoint(self.pos.x, self.pos.y))
                        triangle.append(cmpPoint(x_mid, self.pos.y - 5))
                        triangle.append(cmpPoint(x_right, self.pos.y))
                    elif self.face == FACE_NORTHEAST:
                        triangle.append(cmpPoint(x_mid, self.pos.y))
                        triangle.append(cmpPoint(x_right + 5, self.pos.y - 5))
                        triangle.append(cmpPoint(x_right, y_mid))
                        triangle.append(cmpPoint(x_right, self.pos.y))
                    elif self.face == FACE_SOUTHEAST:
                        triangle.append(cmpPoint(x_right, y_mid))
                        triangle.append(cmpPoint(x_right + 5, y_bottom + 5))
                        triangle.append(cmpPoint(x_mid, y_bottom))
                        triangle.append(cmpPoint(x_right, y_bottom))
                    elif self.face == FACE_SOUTHWEST:
                        triangle.append(cmpPoint(x_mid, y_bottom))
                        triangle.append(cmpPoint(self.pos.x - 5, y_bottom + 5))
                        triangle.append(cmpPoint(self.pos.x, y_mid))
                        triangle.append(cmpPoint(self.pos.x, y_bottom))
                    elif self.face == FACE_NORTHWEST:
                        triangle.append(cmpPoint(self.pos.x, y_mid))
                        triangle.append(cmpPoint(self.pos.x - 5, self.pos.y - 5))
                        triangle.append(cmpPoint(x_mid, self.pos.y))
                        triangle.append(cmpPoint(self.pos.x, self.pos.y))
                    dc.DrawPolygon(triangle)
                    dc.SetBrush(wx.NullBrush)
                    dc.SetPen(wx.NullPen)

                # Draw the heading if needed
                if self.heading:
                    x_adjust = 0
                    y_adjust = 4
                    x_half = self.bmp.GetWidth()/2
                    y_half = self.bmp.GetHeight()/2
                    x_quarter = self.bmp.GetWidth()/4
                    y_quarter = self.bmp.GetHeight()/4
                    x_3quarter = x_quarter*3
                    y_3quarter = y_quarter*3
                    x_full = self.bmp.GetWidth()
                    y_full = self.bmp.GetHeight()
                    x_center = self.pos.x + x_half
                    y_center = self.pos.y + y_half
                    # Remember, the pen/brush must be a different color than the
                    # facing marker!!!!  We'll use black/cyan for starters.
                    # Also notice that we will draw the heading on top of the
                    # larger facing marker.
                    dc.SetPen(wx.BLACK_PEN)
                    dc.SetBrush(wx.CYAN_BRUSH)
                    triangle = []

                    # Figure out which direction to draw the marker!!
                    if self.heading == FACE_NORTH:
                        triangle.append(cmpPoint(x_center - x_quarter, y_center - y_half ))
                        triangle.append(cmpPoint(x_center, y_center - y_3quarter ))
                        triangle.append(cmpPoint(x_center + x_quarter, y_center - y_half ))
                    elif self.heading ==  FACE_SOUTH:
                        triangle.append(cmpPoint(x_center - x_quarter, y_center + y_half ))
                        triangle.append(cmpPoint(x_center, y_center + y_3quarter ))
                        triangle.append(cmpPoint(x_center + x_quarter, y_center + y_half ))
                    elif self.heading == FACE_NORTHEAST:
                        triangle.append(cmpPoint(x_center + x_quarter, y_center - y_half ))
                        triangle.append(cmpPoint(x_center + x_3quarter, y_center - y_3quarter ))
                        triangle.append(cmpPoint(x_center + x_half, y_center - y_quarter ))
                    elif self.heading == FACE_EAST:
                        triangle.append(cmpPoint(x_center + x_half, y_center - y_quarter ))
                        triangle.append(cmpPoint(x_center + x_3quarter, y_center ))
                        triangle.append(cmpPoint(x_center + x_half, y_center + y_quarter ))
                    elif self.heading == FACE_SOUTHEAST:
                        triangle.append(cmpPoint(x_center + x_half, y_center + y_quarter ))
                        triangle.append(cmpPoint(x_center + x_3quarter, y_center + y_3quarter ))
                        triangle.append(cmpPoint(x_center + x_quarter, y_center + y_half ))
                    elif self.heading == FACE_SOUTHWEST:
                        triangle.append(cmpPoint(x_center - x_quarter, y_center + y_half ))
                        triangle.append(cmpPoint(x_center - x_3quarter, y_center + y_3quarter ))
                        triangle.append(cmpPoint(x_center - x_half, y_center + y_quarter ))
                    elif self.heading == FACE_WEST:
                        triangle.append(cmpPoint(x_center - x_half, y_center + y_quarter ))
                        triangle.append(cmpPoint(x_center - x_3quarter, y_center ))
                        triangle.append(cmpPoint(x_center - x_half, y_center - y_quarter ))
                    elif self.heading == FACE_NORTHWEST:
                        triangle.append(cmpPoint(x_center - x_half, y_center - y_quarter ))
                        triangle.append(cmpPoint(x_center - x_3quarter, y_center - y_3quarter ))
                        triangle.append(cmpPoint(x_center - x_quarter, y_center - y_half ))
                    dc.DrawPolygon(triangle)
                    dc.SetBrush(wx.NullBrush)
                    dc.SetPen(wx.NullPen)
                #selected outline
                if self.selected:
                    dc.SetPen(wx.RED_PEN)
                    dc.SetBrush(wx.TRANSPARENT_BRUSH)
                    dc.DrawRectangle(self.pos.x, self.pos.y, self.bmp.GetWidth(), self.bmp.GetHeight())
                    dc.SetBrush(wx.NullBrush)
                    dc.SetPen(wx.NullPen)
                # draw label
                label = mini_layer.get_mini_label(self)
                if len(label):
                    dc.SetTextForeground(wx.RED)
                    (textWidth,textHeight) = dc.GetTextExtent(label)
                    x = self.pos.x +((self.bmp.GetWidth() - textWidth) /2) - 1
                    y = self.pos.y + self.bmp.GetHeight() + 6
                    dc.SetPen(wx.WHITE_PEN)
                    dc.SetBrush(wx.WHITE_BRUSH)
                    dc.DrawRectangle(x,y,textWidth+2,textHeight+2)
                    if (textWidth+2 > self.right):
                        self.right += int((textWidth+2-self.right)/2)+1
                        self.left -= int((textWidth+2-self.right)/2)+1
                    self.bottom = y+textHeight+2-self.pos.y
                    dc.SetPen(wx.NullPen)
                    dc.SetBrush(wx.NullBrush)
                    dc.DrawText(label,x+1,y+1)
                self.top-=5
                self.bottom+=5
                self.left-=5
                self.right+=5
                self.log.log("Exit BmpMiniature->draw->Not Hidden", ORPG_DEBUG)
                return True
        else: self.log.log("Exit BmpMiniature->draw(self, dc, mini_layer, op) return False", ORPG_DEBUG); return False
        self.log.log("Exit BmpMiniature->draw(self, dc, mini_layer, op)", ORPG_DEBUG)

    def toxml(self, action="update"):
        self.log.log("Enter BmpMiniature->toxml(self, " + action + ")", ORPG_DEBUG)
        if action == "del":
            xml_str = "<miniature action='del' id='" + self.id + "'/>"
            self.log.log(xml_str, ORPG_DEBUG)
            self.log.log("Exit BmpMiniature->toxml(self, " + action + ")", ORPG_DEBUG)
            return xml_str
        xml_str = "<miniature"
        xml_str += " action='" + action + "'"
        xml_str += " label='" + self.label + "'"
        xml_str+= " id='" + self.id + "'"
        if self.pos != None:
            xml_str += " posx='" + str(self.pos.x) + "'"
            xml_str += " posy='" + str(self.pos.y) + "'"
        if self.heading != None: xml_str += " heading='" + str(self.heading) + "'"
        if self.face != None: xml_str += " face='" + str(self.face) + "'"
        if self.path != None: xml_str += " path='" + urllib.quote(self.path).replace('%3A', ':') + "'"
        if self.locked: xml_str += "  locked='1'"
        else: xml_str += "  locked='0'"
        if self.hide: xml_str += " hide='1'"
        else: xml_str += " hide='0'"
        if self.snap_to_align != None: xml_str += " align='" + str(self.snap_to_align) + "'"
        if self.id != None: xml_str += " zorder='" + str(self.zorder) + "'"
        if self.width != None: xml_str += " width='" + str(self.width) + "'"
        if self.height != None: xml_str += " height='" + str(self.height) + "'"
        if self.local:
            xml_str += ' local="' + str(self.local) + '"'
            xml_str += ' localPath="' + str(urllib.quote(self.localPath).replace('%3A', ':')) + '"'
            xml_str += ' localTime="' + str(self.localTime) + '"'
        xml_str += " />"
        self.log.log(xml_str, ORPG_DEBUG)
        self.log.log("Exit BmpMiniature->toxml(self, " + action + ")", ORPG_DEBUG)
        if (action == "update" and self.isUpdated) or action == "new":
            self.isUpdated = False
            return xml_str
        else: return ''

    def takedom(self, xml_dom):
        self.log.log("Enter BmpMiniature->takedom(self, xml_dom)", ORPG_DEBUG)
        self.id = xml_dom.getAttribute("id")
        self.log.log("self.id=" + str(self.id), ORPG_DEBUG)
        if xml_dom.hasAttribute("posx"):
            self.pos.x = int(xml_dom.getAttribute("posx"))
            self.log.log("self.pos.x=" + str(self.pos.x), ORPG_DEBUG)
        if xml_dom.hasAttribute("posy"):
            self.pos.y = int(xml_dom.getAttribute("posy"))
            self.log.log("self.pos.y=" + str(self.pos.y), ORPG_DEBUG)
        if xml_dom.hasAttribute("heading"):
            self.heading = int(xml_dom.getAttribute("heading"))
            self.log.log("self.heading=" + str(self.heading), ORPG_DEBUG)
        if xml_dom.hasAttribute("face"):
            self.face = int(xml_dom.getAttribute("face"))
            self.log.log("self.face=" + str(self.face), ORPG_DEBUG)
        if xml_dom.hasAttribute("path"):
            self.path = urllib.unquote(xml_dom.getAttribute("path"))
            self.set_bmp(ImageHandler.load(self.path, 'miniature', self.id))
            self.log.log("self.path=" + self.path, ORPG_DEBUG)
        if xml_dom.hasAttribute("locked"):
            if xml_dom.getAttribute("locked") == '1' or xml_dom.getAttribute("locked") == 'True': self.locked = True
            else: self.locked = False
            self.log.log("self.locked=" + str(self.locked), ORPG_DEBUG)
        if xml_dom.hasAttribute("hide"):
            if xml_dom.getAttribute("hide") == '1' or xml_dom.getAttribute("hide") == 'True': self.hide = True
            else: self.hide = False
            self.log.log("self.hide=" + str(self.hide), ORPG_DEBUG)
        if xml_dom.hasAttribute("label"):
            self.label = xml_dom.getAttribute("label")
            self.log.log("self.label=" + self.label, ORPG_DEBUG)
        if xml_dom.hasAttribute("zorder"):
            self.zorder = int(xml_dom.getAttribute("zorder"))
            self.log.log("self.zorder=" + str(self.zorder), ORPG_DEBUG)
        if xml_dom.hasAttribute("align"):
            if xml_dom.getAttribute("align") == '1' or xml_dom.getAttribute("align") == 'True': self.snap_to_align = 1
            else: self.snap_to_align = 0
            self.log.log("self.snap_to_align=" + str(self.snap_to_align), ORPG_DEBUG)
        if xml_dom.hasAttribute("width"):
            self.width = int(xml_dom.getAttribute("width"))
            self.log.log("self.width=" + str(self.width), ORPG_DEBUG)
        if xml_dom.hasAttribute("height"):
            self.height = int(xml_dom.getAttribute("height"))
            self.log.log("self.height=" + str(self.height), ORPG_DEBUG)
        self.log.log("Exit BmpMiniature->takedom(self, xml_dom)", ORPG_DEBUG)

##-----------------------------
## miniature layer
##-----------------------------
class miniature_layer(layer_base):
    def __init__(self, canvas):
        self.canvas = canvas
        self.log = self.canvas.log
        self.log.log("Enter miniature_layer", ORPG_DEBUG)
        self.settings = self.canvas.settings
        layer_base.__init__(self)
        self.id = -1 #added.
        self.miniatures = []
        self.serial_number = 0

        # Set the font of the labels to be the same as the chat window
        # only smaller.
        font_size = int(self.settings.get_setting('defaultfontsize'))
        if (font_size >= 10): font_size -= 2
        self.label_font = wx.Font(font_size, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL,
                                  False, self.settings.get_setting('defaultfont'))
        self.log.log("Exit miniature_layer", ORPG_DEBUG)

    def next_serial(self):
        self.log.log("Enter miniature_layer->next_serial(self)", ORPG_DEBUG)
        self.serial_number += 1
        self.log.log("Exit miniature_layer->next_serial(self)", ORPG_DEBUG)
        return self.serial_number

    def get_next_highest_z(self):
        self.log.log("Enter miniature_layer->get_next_highest_z(self)", ORPG_DEBUG)
        z = len(self.miniatures)+1
        self.log.log("Exit miniature_layer->get_next_highest_z(self)", ORPG_DEBUG)
        return z

    def cleanly_collapse_zorder(self):
        self.log.log("Enter miniature_layer->cleanly_collapse_zorder(self)", ORPG_DEBUG)
        #  lock the zorder stuff
        sorted_miniatures = self.miniatures[:]
        sorted_miniatures.sort(cmp_zorder)
        i = 0
        for mini in sorted_miniatures:
            mini.zorder = i
            i = i + 1
        self.log.log("Exit miniature_layer->cleanly_collapse_zorder(self)", ORPG_DEBUG)
        #  unlock the zorder stuff

    def collapse_zorder(self):
        self.log.log("Enter miniature_layer->collapse_zorder(self)", ORPG_DEBUG)
        #  lock the zorder stuff
        sorted_miniatures = self.miniatures[:]
        sorted_miniatures.sort(cmp_zorder)
        i = 0
        for mini in sorted_miniatures:
            if (mini.zorder != MIN_STICKY_BACK) and (mini.zorder != MIN_STICKY_FRONT): mini.zorder = i
            else: pass
            i = i + 1
        self.log.log("Exit miniature_layer->collapse_zorder(self)", ORPG_DEBUG)
        #  unlock the zorder stuff

    def rollback_serial(self):
        self.log.log("Enter miniature_layer->rollback_serial(self)", ORPG_DEBUG)
        self.serial_number -= 1
        self.log.log("Exit miniature_layer->rollback_serial(self)", ORPG_DEBUG)

    def add_miniature(self, id, path, pos=cmpPoint(0,0), label="", heading=FACE_NONE, 
            face=FACE_NONE, width=0, height=0, local=False, localPath='', localTime=-1):
        self.log.log("Enter miniature_layer->add_miniature(self, id, path, pos, label, heading, face, width, height)", 
            ORPG_DEBUG)
        self.log.log("Before mini creation: " + str(self.get_next_highest_z()), ORPG_DEBUG)
        bmp = ImageHandler.load(path, 'miniature', id)
        if bmp:
            mini = BmpMiniature(id, path, bmp, pos, heading, face, label, 
                zorder=self. get_next_highest_z(), width=width, 
                height=height, log=self.log, local=local, localPath=localPath, localTime=localTime)
            self.log.log("After mini creation:" + str(self.get_next_highest_z()), ORPG_DEBUG)
            self.miniatures.append(mini)
            self.log.log("After mini addition:" + str(self.get_next_highest_z()), ORPG_DEBUG)
            xml_str = "<map><miniatures>"
            xml_str += mini.toxml("new")
            xml_str += "</miniatures></map>"
            self.canvas.frame.session.send(xml_str)
        else: self.log.log("Invalid image " + path + " has been ignored!", ORPG_DEBUG)
        self.log.log("Exit miniature_layer->add_miniature(self, id, path, pos, label, heading, face, width, height)", 
            ORPG_DEBUG)

    def get_miniature_by_id(self, id):
        self.log.log("Enter miniature_layer->get_miniature_by_id(self, id)", ORPG_DEBUG)
        for mini in self.miniatures:
            if str(mini.id) == str(id):
                self.log.log("Exit miniature_layer->get_miniature_by_id(self, id) return miniID: " + str(id), ORPG_DEBUG)
                return mini
        self.log.log("Exit miniature_layer->get_miniature_by_id(self, id) return None", ORPG_DEBUG)
        return None

    def del_miniature(self, min):
        self.log.log("Enter miniature_layer->del_miniature(self, min)", ORPG_DEBUG)
        xml_str = "<map><miniatures>"
        xml_str += min.toxml("del")
        xml_str += "</miniatures></map>"
        self.canvas.frame.session.send(xml_str)
        self.miniatures.remove(min)
        del min
        self.collapse_zorder()
        self.log.log("Exit miniature_layer->del_miniature(self, min)", ORPG_DEBUG)

    def del_all_miniatures(self):
        self.log.log("Enter miniature_layer->del_all_miniatures(self)", ORPG_DEBUG)
        while len(self.miniatures):
            min = self.miniatures.pop()
            del min
        self.collapse_zorder()
        self.log.log("Exit miniature_layer->del_all_miniatures(self)", ORPG_DEBUG)

    def layerDraw(self, dc, topleft, size):
        self.log.log("Enter miniature_layer->layerDraw(self, dc, topleft, size)", ORPG_DEBUG)
        dc.SetFont(self.label_font)
        sorted_miniatures = self.miniatures[:]
        sorted_miniatures.sort(cmp_zorder)
        for m in sorted_miniatures:
            if (m.pos.x>topleft[0]-m.right and
                m.pos.y>topleft[1]-m.bottom and
                m.pos.x<topleft[0]+size[0]-m.left and
                m.pos.y<topleft[1]+size[1]-m.top):
                m.draw(dc, self)
        self.log.log("Exit miniature_layer->layerDraw(self, dc, topleft, size)", ORPG_DEBUG)

    def find_miniature(self, pt, only_unlocked=False):
        self.log.log("Enter miniature_layer->find_miniature(self, pt, only_unlocked)", ORPG_DEBUG)
        min_list = []
        for m in self.miniatures:
            if m.hit_test(pt):
                if m.hide and self.canvas.frame.session.my_role() != self.canvas.frame.session.ROLE_GM: continue
                if only_unlocked and not m.locked: min_list.append(m)
                elif not only_unlocked and m.locked: min_list.append(m)
                else: continue
        if len(min_list) > 0:
            self.log.log("Exit miniature_layer->find_miniature(self, pt, only_unlocked)", ORPG_DEBUG)
            return min_list
        else: self.log.log("Exit miniature_layer->find_miniature(self, pt, only_unlocked)", ORPG_DEBUG); return None

    def layerToXML(self, action="update"):
        """ format  """
        self.log.log("Enter miniature_layer->layerToXML(self, " + action + ")", ORPG_DEBUG)
        minis_string = ""
        if self.miniatures:
            for m in self.miniatures: minis_string += m.toxml(action)
        if minis_string != '':
            s = "<miniatures"
            s += " serial='" + str(self.serial_number) + "'"
            s += ">"
            s += minis_string
            s += "</miniatures>"
            self.log.log("Exit miniature_layer->layerToXML(self, " + action + ")", ORPG_DEBUG)
            return s
        else: self.log.log("Exit miniature_layer->layerToXML(self, " + action + ") return None", ORPG_DEBUG); return ""

    def layerTakeDOM(self, xml_dom):
        self.log.log("Enter miniature_layer->layerTakeDOM(self, xml_dom)", ORPG_DEBUG)
        if xml_dom.hasAttribute('serial'):
            self.serial_number = int(xml_dom.getAttribute('serial'))
        children = xml_dom._get_childNodes()
        for c in children:
            action = c.getAttribute("action")
            id = c.getAttribute('id')
            if action == "del": 
                mini = self.get_miniature_by_id(id)
                if mini:
                    self.miniatures.remove(mini)
                    del mini
                else: self.log.log("Map Synchronization Error :: Update of unknown mini attempted", ORPG_DEBUG)
            elif action == "new":
                pos = cmpPoint(int(c.getAttribute('posx')),int(c.getAttribute('posy')))
                path = urllib.unquote(c.getAttribute('path'))
                label = c.getAttribute('label')
                height = width = heading = face = snap_to_align = zorder = 0
                locked = hide = False
                if c.hasAttribute('height'): height = int(c.getAttribute('height'))
                if c.hasAttribute('width'): width = int(c.getAttribute('width'))
                if c.getAttribute('locked') == 'True' or c.getAttribute('locked') == '1': locked = True
                if c.getAttribute('hide') == 'True' or c.getAttribute('hide') == '1': hide = True
                if c.getAttribute('heading'): heading = int(c.getAttribute('heading'))
                if c.hasAttribute('face'): face = int(c.getAttribute('face'))
                if c.hasAttribute('align'): snap_to_align = int(c.getAttribute('align'))
                if c.getAttribute('zorder'): zorder = int(c.getAttribute('zorder'))
                min = BmpMiniature(id, path, ImageHandler.load(path, 'miniature', id), pos, heading, 
                    face, label, locked, hide, snap_to_align, zorder, width, height, self.log)
                self.miniatures.append(min)
                if c.hasAttribute('local') and c.getAttribute('local') == 'True' and os.path.exists(urllib.unquote(c.getAttribute('localPath'))):
                    localPath = urllib.unquote(c.getAttribute('localPath'))
                    local = True
                    localTime = float(c.getAttribute('localTime'))
                    if localTime-time.time() <= 144000:
                        file = open(localPath, "rb")
                        imgdata = file.read()
                        file.close()
                        filename = os.path.split(localPath)
                        (imgtype,j) = mimetypes.guess_type(filename[1])
                        postdata = urllib.urlencode({'filename':filename[1], 'imgdata':imgdata, 'imgtype':imgtype})
                        thread.start_new_thread(self.upload, (postdata, localPath, True))
                #  collapse the zorder.  If the client behaved well, then nothing should change.
                #    Otherwise, this will ensure that there's some kind of z-order
                self.collapse_zorder()
            else:
                mini = self.get_miniature_by_id(id)
                if mini: mini.takedom(c)
                else: self.log.log("Map Synchronization Error :: Update of unknown mini attempted", ORPG_DEBUG)
        self.log.log("Exit miniature_layer->layerTakeDOM(self, xml_dom)", ORPG_DEBUG)

    def upload(self, postdata, filename, modify=False, pos=cmpPoint(0,0)):
        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 not modify:
                    start = path.rfind("/") + 1
                    if self.canvas.parent.layer_handlers[2].auto_label: min_label = path[start:len(path)-4]
                    else: min_label = ""
                    id = 'mini-' + self.canvas.frame.session.get_next_id()
                    self.add_miniature(id, path, pos=pos, label=min_label, local=True, 
                        localPath=filename, localTime=time.time())
                else:
                    self.miniatures[len(self.miniatures)-1].local = True
                    self.miniatures[len(self.miniatures)-1].localPath = filename
                    self.miniatures[len(self.miniatures)-1].localTime = time.time()
                    self.miniatures[len(self.miniatures)-1].path = path
            else:
                print xml_dom.getAttribute('msg')
        except Exception, e:
            print e
            print recvdata
        urllib.urlcleanup()
        self.lock.release()
####################################################################
        ## helper function

    def get_mini_label(self, mini):
        # override this to change the label displayed under each mini (and the label on hidden minis)
        return mini.label