diff orpg/mapper/miniatures_handler.py @ 0:4385a7d0efd1 grumpy-goblin

Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author sirebral
date Tue, 14 Jul 2009 16:41:58 -0500
parents
children d63ad196cc0d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/orpg/mapper/miniatures_handler.py	Tue Jul 14 16:41:58 2009 -0500
@@ -0,0 +1,860 @@
+# 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/whiteboard_hander.py
+# Author: OpenRPG Team
+# Maintainer:
+# Version:
+#   $Id: miniatures_handler.py,v 1.43 2007/12/07 20:39:50 digitalxero Exp $
+#
+# Description: Miniature layer handler
+#
+__version__ = "$Id: miniatures_handler.py,v 1.43 2007/12/07 20:39:50 digitalxero Exp $"
+
+from base_handler import *
+from min_dialogs import *
+import thread
+import time
+import mimetypes
+import urllib
+import xml.dom.minidom as minidom
+import wx
+from grid import GRID_RECTANGLE
+from grid import GRID_HEXAGON
+from grid import GRID_ISOMETRIC
+LABEL_TOOL = wx.NewId()
+LAYER_TOOL = wx.NewId()
+MIN_LIST_TOOL = wx.NewId()
+MIN_TOOL = wx.NewId()
+MIN_URL = wx.NewId()
+SERIAL_TOOL = wx.NewId()
+MIN_MOVE = wx.NewId()
+MIN_REMOVE = wx.NewId()
+MIN_PROP_DLG = wx.NewId()
+MIN_FACING_NONE = wx.NewId()
+MIN_FACING_MATCH = wx.NewId()
+MIN_FACING_EAST = wx.NewId()
+MIN_FACING_WEST = wx.NewId()
+MIN_FACING_NORTH = wx.NewId()
+MIN_FACING_SOUTH = wx.NewId()
+MIN_FACING_NORTHEAST = wx.NewId()
+MIN_FACING_SOUTHEAST = wx.NewId()
+MIN_FACING_SOUTHWEST = wx.NewId()
+MIN_FACING_NORTHWEST = wx.NewId()
+MIN_HEADING_NONE = wx.NewId()
+MIN_HEADING_MATCH = wx.NewId()
+MIN_HEADING_EAST = wx.NewId()
+MIN_HEADING_WEST = wx.NewId()
+MIN_HEADING_NORTH = wx.NewId()
+MIN_HEADING_SOUTH = wx.NewId()
+MIN_HEADING_NORTHEAST = wx.NewId()
+MIN_HEADING_SOUTHEAST = wx.NewId()
+MIN_HEADING_SOUTHWEST = wx.NewId()
+MIN_HEADING_NORTHWEST = wx.NewId()
+MIN_HEADING_SUBMENU = wx.NewId()
+MIN_FACING_SUBMENU = wx.NewId()
+MIN_ALIGN_SUBMENU = wx.NewId()
+MIN_ALIGN_GRID_CENTER = wx.NewId()
+MIN_ALIGN_GRID_TL = wx.NewId()
+MIN_TITLE_HACK = wx.NewId()
+MIN_TO_GAMETREE = wx.NewId()
+MIN_BACK_ONE = wx.NewId()
+MIN_FORWARD_ONE = wx.NewId()
+MIN_TO_BACK = wx.NewId()
+MIN_TO_FRONT = wx.NewId()
+MIN_LOCK_BACK = wx.NewId()
+MIN_LOCK_FRONT = wx.NewId()
+MIN_FRONTBACK_UNLOCK = wx.NewId()
+MIN_ZORDER_SUBMENU = wx.NewId()
+MIN_SHOW_HIDE = wx.NewId()
+MIN_LOCK_UNLOCK = wx.NewId()
+MAP_REFRESH_MINI_URLS = wx.NewId()
+
+class myFileDropTarget(wx.FileDropTarget):
+    def __init__(self, handler):
+        wx.FileDropTarget.__init__(self)
+        self.m_handler = handler
+    def OnDropFiles(self, x, y, filenames):
+        self.m_handler.on_drop_files(x, y, filenames)
+
+class miniatures_handler(base_layer_handler):
+
+    def __init__(self, parent, id, canvas):
+        self.sel_min = None
+        self.auto_label = 1
+        self.use_serial = 1
+        self.auto_label_cb = None
+        self.canvas = canvas
+        self.settings = self.canvas.settings
+        self.mini_rclick_menu_extra_items = {}
+        self.background_rclick_menu_extra_items = {}
+        base_layer_handler.__init__(self, parent, id, canvas)
+        # id is the index of the last good menu choice or 'None'
+        # if the last menu was left without making a choice
+        # should be -1 at other times to prevent events overlapping
+        self.lastMenuChoice = None
+        self.drag_mini = None
+        self.tooltip_delay_miliseconds = 500
+        self.tooltip_timer = wx.CallLater(self.tooltip_delay_miliseconds, self.on_tooltip_timer)
+        self.tooltip_timer.Stop()
+        dt = myFileDropTarget(self)
+        self.canvas.SetDropTarget(dt)
+   #     wxInitAllImageHandlers()
+
+    def build_ctrls(self):
+        base_layer_handler.build_ctrls(self)
+        # add controls in reverse order! (unless you want them after the default tools)
+        self.auto_label_cb = wx.CheckBox(self, wx.ID_ANY, ' Auto Label ', (-1,-1),(-1,-1))
+        self.auto_label_cb.SetValue(self.auto_label)
+        self.min_url = wx.ComboBox(self, wx.ID_ANY, "http://", style=wx.CB_DROPDOWN | wx.CB_SORT)
+        self.localBrowse = wx.Button(self, wx.ID_ANY, 'Browse')
+        minilist = createMaskedButton( self, orpg.dirpath.dir_struct["icon"]+'questionhead.gif', 'Edit miniature properties', wx.ID_ANY)
+        miniadd = wx.Button(self, wx.ID_OK, "Add Miniature", style=wx.BU_EXACTFIT)
+        self.sizer.Add(self.auto_label_cb,0,wx.ALIGN_CENTER)
+        self.sizer.Add(self.min_url, 1, wx.ALIGN_CENTER)
+        self.sizer.Add(miniadd, 0, wx.EXPAND)
+        self.sizer.Add(self.localBrowse, 0, wx.EXPAND)
+        self.sizer.Add(wx.Size(20,25))
+        self.sizer.Add(minilist, 0, wx.EXPAND )
+        self.Bind(wx.EVT_BUTTON, self.on_min_list, minilist)
+        self.Bind(wx.EVT_BUTTON, self.on_miniature, miniadd)
+        self.Bind(wx.EVT_BUTTON, self.on_browse, self.localBrowse)
+        self.Bind(wx.EVT_CHECKBOX, self.on_label, self.auto_label_cb)
+
+    def on_browse(self, evt):
+        if not self.role_is_gm_or_player():
+            return
+        dlg = wx.FileDialog(None, "Select a Miniature to load", orpg.dirpath.dir_struct["user"]+'webfiles/', wildcard="Image files (*.bmp, *.gif, *.jpg, *.png)|*.bmp;*.gif;*.jpg;*.png", style=wx.OPEN)
+        if not dlg.ShowModal() == wx.ID_OK:
+            dlg.Destroy()
+            return
+        file = open(dlg.GetPath(), "rb")
+        imgdata = file.read()
+        file.close()
+        filename = dlg.GetFilename()
+        (imgtype,j) = mimetypes.guess_type(filename)
+        postdata = urllib.urlencode({'filename':filename, 'imgdata':imgdata, 'imgtype':imgtype})
+        if self.settings.get_setting('LocalorRemote') == 'Remote':
+            # make the new mini appear in top left of current viewable map
+            dc = wx.ClientDC(self.canvas)
+            self.canvas.PrepareDC(dc)
+            dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+            x = dc.DeviceToLogicalX(0)
+            y = dc.DeviceToLogicalY(0)
+            thread.start_new_thread(self.canvas.layers['miniatures'].upload, (postdata, dlg.GetPath()), {'pos':cmpPoint(x,y)})
+        else:
+            min_url = self.settings.get_setting('LocalImageBaseURL') + filename
+            if dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles/Textures' or dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles\Textures': min_url = self.settings.get_setting('LocalImageBaseURL') + 'Textures/' + filename
+            if dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles/Maps' or dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles\Maps': min_url = self.settings.get_setting('ImageServerBaseURL') + 'Maps/' + filename
+            if dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles/Miniatures' or dlg.GetDirectory() == orpg.dirpath.dir_struct["user"]+'webfiles\Miniatures': min_url = self.settings.get_setting('LocalImageBaseURL') + 'Miniatures/' + filename
+            # build url
+            if min_url == "" or min_url == "http://":
+                return
+            if min_url[:7] != "http://" :
+                min_url = "http://" + min_url
+            # make label
+            if self.auto_label and min_url[-4:-3] == '.':
+                start = min_url.rfind("/") + 1
+                min_label = min_url[start:len(min_url)-4]
+                if self.use_serial:
+                    min_label = '%s %d' % ( min_label, self.canvas.layers['miniatures'].next_serial() )
+            else:
+                min_label = ""
+            if self.min_url.FindString(min_url) == -1:
+                self.min_url.Append(min_url)
+            try:
+                id = 'mini-' + self.canvas.frame.session.get_next_id()
+                # make the new mini appear in top left of current viewable map
+                dc = wx.ClientDC(self.canvas)
+                self.canvas.PrepareDC(dc)
+                dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+                x = dc.DeviceToLogicalX(0)
+                y = dc.DeviceToLogicalY(0)
+                self.canvas.layers['miniatures'].add_miniature(id, min_url, pos=cmpPoint(x,y), label=min_label)
+            except:
+                # When there is an exception here, we should be decrementing the serial_number for reuse!!
+                unablemsg= "Unable to load/resolve URL: " + min_url + " on resource \"" + min_label + "\"!!!\n\n"
+                #print unablemsg
+                dlg = wx.MessageDialog(self,unablemsg, 'Url not found',wx.ICON_EXCLAMATION)
+                dlg.ShowModal()
+                dlg.Destroy()
+                self.canvas.layers['miniatures'].rollback_serial()
+            self.canvas.send_map_data()
+            self.canvas.Refresh(False)
+
+
+    def build_menu(self,label = "Miniature"):
+        base_layer_handler.build_menu(self,label)
+        self.main_menu.AppendSeparator()
+        self.main_menu.Append(LABEL_TOOL,"&Auto label","",1)
+        self.main_menu.Check(LABEL_TOOL,self.auto_label)
+        self.main_menu.Append(SERIAL_TOOL,"&Number minis","",1)
+        self.main_menu.Check(SERIAL_TOOL, self.use_serial)
+        self.main_menu.Append(MAP_REFRESH_MINI_URLS,"&Refresh miniatures")       #  Add the menu item
+        self.main_menu.AppendSeparator()
+        self.main_menu.Append(MIN_MOVE, "Move")
+        self.canvas.Bind(wx.EVT_MENU, self.on_map_board_menu_item, id=MAP_REFRESH_MINI_URLS)          #  Set the handler
+        self.canvas.Bind(wx.EVT_MENU, self.on_label, id=LABEL_TOOL)
+        self.canvas.Bind(wx.EVT_MENU, self.on_serial, id=SERIAL_TOOL)
+        # build miniature meenu
+        self.min_menu = wx.Menu()
+        # Rectangles and hexagons require slightly different menus because of
+        # facing and heading possibilities.
+        heading_menu = wx.Menu()
+        face_menu = wx.Menu()
+        face_menu.Append(MIN_FACING_NONE,"&None")
+        face_menu.Append(MIN_FACING_NORTH,"&North")
+        face_menu.Append(MIN_FACING_NORTHEAST,"Northeast")
+        face_menu.Append(MIN_FACING_EAST,"East")
+        face_menu.Append(MIN_FACING_SOUTHEAST,"Southeast")
+        face_menu.Append(MIN_FACING_SOUTH,"&South")
+        face_menu.Append(MIN_FACING_SOUTHWEST,"Southwest")
+        face_menu.Append(MIN_FACING_WEST,"West")
+        face_menu.Append(MIN_FACING_NORTHWEST,"Northwest")
+        heading_menu.Append(MIN_HEADING_NONE,"&None")
+        heading_menu.Append(MIN_HEADING_NORTH,"&North")
+        heading_menu.Append(MIN_HEADING_NORTHEAST,"Northeast")
+        heading_menu.Append(MIN_HEADING_EAST,"East")
+        heading_menu.Append(MIN_HEADING_SOUTHEAST,"Southeast")
+        heading_menu.Append(MIN_HEADING_SOUTH,"&South")
+        heading_menu.Append(MIN_HEADING_SOUTHWEST,"Southwest")
+        heading_menu.Append(MIN_HEADING_WEST,"West")
+        heading_menu.Append(MIN_HEADING_NORTHWEST,"Northwest")
+        align_menu = wx.Menu()
+        align_menu.Append(MIN_ALIGN_GRID_CENTER,"&Center")
+        align_menu.Append(MIN_ALIGN_GRID_TL,"&Top-Left")
+        #  This is a hack to simulate a menu title, due to problem in Linux
+        if wx.Platform == '__WXMSW__':
+            self.min_menu.SetTitle(label)
+        else:
+            self.min_menu.Append(MIN_TITLE_HACK,label)
+            self.min_menu.AppendSeparator()
+        self.min_menu.Append(MIN_SHOW_HIDE,"Show / Hide")
+        self.min_menu.Append(MIN_LOCK_UNLOCK, "Lock / Unlock")
+        self.min_menu.Append(MIN_REMOVE,"&Remove")
+        self.min_menu.Append(MIN_TO_GAMETREE,"To &Gametree")
+        self.min_menu.AppendMenu(MIN_HEADING_SUBMENU,"Set &Heading",heading_menu)
+        self.min_menu.AppendMenu(MIN_FACING_SUBMENU,"Set &Facing",face_menu)
+        self.min_menu.AppendMenu(MIN_ALIGN_SUBMENU,"Snap-to &Alignment",align_menu)
+        self.min_menu.AppendSeparator()
+        zorder_menu = wx.Menu()
+        zorder_menu.Append(MIN_BACK_ONE,"Back one")
+        zorder_menu.Append(MIN_FORWARD_ONE,"Forward one")
+        zorder_menu.Append(MIN_TO_BACK,"To back")
+        zorder_menu.Append(MIN_TO_FRONT,"To front")
+        zorder_menu.AppendSeparator()
+        zorder_menu.Append(MIN_LOCK_BACK,"Lock to back")
+        zorder_menu.Append(MIN_LOCK_FRONT,"Lock to front")
+        zorder_menu.Append(MIN_FRONTBACK_UNLOCK,"Unlock Front/Back")
+        self.min_menu.AppendMenu(MIN_ZORDER_SUBMENU, "Miniature Z-Order",zorder_menu)
+        #self.min_menu.Append(MIN_LOCK,"&Lock")
+        self.min_menu.AppendSeparator()
+        self.min_menu.Append(MIN_PROP_DLG,"&Properties")
+        self.min_menu.AppendSeparator()
+        self.min_menu.Append(MIN_MOVE, "Move")
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_MOVE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_SHOW_HIDE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_UNLOCK)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_REMOVE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_GAMETREE)
+        #self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_PROP_DLG)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NONE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_EAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_WEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTH)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTH)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTHEAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTHEAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTHWEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTHWEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NONE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_EAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_WEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTH)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTH)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTHEAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTHEAST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTHWEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTHWEST)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_ALIGN_GRID_CENTER)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_ALIGN_GRID_TL)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_BACK_ONE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FORWARD_ONE)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_BACK)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_FRONT)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_BACK)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_FRONT)
+        self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FRONTBACK_UNLOCK)
+        ######### add plugin added menu items #########
+        if len(self.mini_rclick_menu_extra_items)>0:
+            self.min_menu.AppendSeparator()
+            for item in self.mini_rclick_menu_extra_items.items():
+                self.min_menu.Append(item[1], item[0])
+        if len(self.background_rclick_menu_extra_items)>0:
+            self.main_menu.AppendSeparator()
+            for item in self.background_rclick_menu_extra_items.items():
+                self.main_menu.Append(item[1], item[0])
+
+    def do_min_menu(self,pos):
+        self.canvas.PopupMenu(self.min_menu,pos)
+
+    def do_min_select_menu(self, min_list, pos):
+        # to prevent another event being processed
+        self.lastMenuChoice = None
+        self.min_select_menu = wx.Menu()
+        self.min_select_menu.SetTitle("Select Miniature")
+        loop_count = 1
+        try:
+            for m in min_list:
+                # Either use the miniatures label for the selection list
+                if m.label:
+                    self.min_select_menu.Append(loop_count, m.label)
+                # Or use part of the images filename as an identifier
+                else:
+                    string_split = string.split(m.path,"/",)
+                    last_string = string_split[len(string_split)-1]
+                    self.min_select_menu.Append(loop_count, 'Unlabeled - ' + last_string[:len(last_string)-4])
+                self.canvas.Bind(wx.EVT_MENU, self.min_selected, id=loop_count)
+                loop_count += 1
+            self.canvas.PopupMenu(self.min_select_menu,pos)
+        except:
+            pass
+
+    def min_selected(self,evt):
+        # this is the callback function for the menu that is used to choose
+        # between minis when you right click, left click or left double click
+        # on a stack of two or more
+        self.canvas.Refresh(False)
+        self.canvas.send_map_data()
+        self.lastMenuChoice = evt.GetId()-1
+
+    def on_min_menu_item(self,evt):
+        id = evt.GetId()
+        if id == MIN_MOVE:
+            if self.sel_min:
+                self.moveSelectedMini(self.last_rclick_pos)
+                self.deselectAndRefresh()
+            return
+        elif id == MIN_REMOVE:
+            self.canvas.layers['miniatures'].del_miniature(self.sel_rmin)
+        elif id == MIN_TO_GAMETREE:
+            min_xml = self.sel_rmin.toxml(action="new")
+            node_begin = "<nodehandler module='map_miniature_nodehandler' class='map_miniature_handler' name='"
+            if self.sel_rmin.label:
+                node_begin += self.sel_rmin.label + "'"
+            else:
+                node_begin += "Unnamed Miniature'"
+            node_begin += ">"
+	    gametree = open_rpg.get_component('tree')
+            node_xml = node_begin + min_xml + '</nodehandler>'
+            #print "Sending this XML to insert_xml:" + node_xml
+            gametree.insert_xml(node_xml)
+        elif id == MIN_SHOW_HIDE:
+            if self.sel_rmin.hide:
+                self.sel_rmin.hide = 0
+            else:
+                self.sel_rmin.hide = 1
+        elif id == MIN_LOCK_UNLOCK:
+            if self.sel_rmin.locked:
+                self.sel_rmin.locked = False
+            else:
+                self.sel_rmin.locked = True
+            if self.sel_rmin == self.sel_min:
+                # when we lock / unlock the selected mini make sure it isn't still selected
+                # or it might easily get moved by accident and be hard to move back
+                self.sel_min.selected = False
+                self.sel_min.isUpdated = True
+                self.sel_min = None
+	recycle_bin = {MIN_HEADING_NONE: FACE_NONE, MIN_HEADING_NORTH: FACE_NORTH, MIN_HEADING_NORTHWEST: FACE_NORTHWEST, MIN_HEADING_NORTHEAST: FACE_NORTHEAST, MIN_HEADING_EAST: FACE_EAST, MIN_HEADING_SOUTHEAST: FACE_SOUTHEAST, MIN_HEADING_SOUTHWEST: FACE_SOUTHWEST, MIN_HEADING_SOUTH: FACE_SOUTH, MIN_HEADING_WEST: FACE_WEST}
+	if recycle_bin.has_key(id):
+	    self.sel_rmin.heading = recycle_bin[id]
+	    recycle_bin = {}
+	recycle_bin = {MIN_FACING_NONE: FACE_NONE, MIN_FACING_NORTH: FACE_NORTH, MIN_FACING_NORTHWEST: FACE_NORTHWEST, MIN_FACING_NORTHEAST: FACE_NORTHEAST, MIN_FACING_EAST: FACE_EAST, MIN_FACING_SOUTHEAST: FACE_SOUTHEAST, MIN_FACING_SOUTHWEST: FACE_SOUTHWEST, MIN_FACING_SOUTH: FACE_SOUTH, MIN_FACING_WEST: FACE_WEST}
+	if recycle_bin.has_key(id):
+	    self.sel_rmin.face = recycle_bin[id]
+	    recycle_bin = {}
+        elif id == MIN_ALIGN_GRID_CENTER:
+            self.sel_rmin.snap_to_align = SNAPTO_ALIGN_CENTER
+        elif id == MIN_ALIGN_GRID_TL:
+            self.sel_rmin.snap_to_align = SNAPTO_ALIGN_TL
+        elif id == MIN_PROP_DLG:
+            old_lock_value = self.sel_rmin.locked
+            dlg = min_edit_dialog(self.canvas.frame.GetParent(),self.sel_rmin)
+            if dlg.ShowModal() == wx.ID_OK:
+                if self.sel_rmin == self.sel_min and self.sel_rmin.locked != old_lock_value:
+                    # when we lock / unlock the selected mini make sure it isn't still selected
+                    # or it might easily get moved by accident and be hard to move back
+                    self.sel_min.selected = False
+                    self.sel_min.isUpdated = True
+                    self.sel_min = None
+                self.canvas.Refresh(False)
+                self.canvas.send_map_data()
+                return
+
+        elif id == MIN_BACK_ONE:
+            #  This assumes that we always start out with a z-order
+            #     that starts at 0 and goes up to the number of
+            #     minis - 1.  If this isn't the case, then execute
+            #     a self.canvas.layers['miniatures'].collapse_zorder()
+            #     before getting the oldz to test
+            #  Save the selected minis current z-order
+            oldz = self.sel_rmin.zorder
+            # Make sure the mini isn't sticky front or back
+            if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
+		##   print "old z-order = " + str(oldz)
+                self.sel_rmin.zorder -= 1
+                #  Re-collapse to normalize
+                #  Note:  only one update (with the final values) will be sent
+                self.canvas.layers['miniatures'].collapse_zorder()
+
+        elif id == MIN_FORWARD_ONE:
+            #  This assumes that we always start out with a z-order
+            #     that starts at 0 and goes up to the number of
+            #     minis - 1.  If this isn't the case, then execute
+            #     a self.canvas.layers['miniatures'].collapse_zorder()
+            #     before getting the oldz to test
+            #  Save the selected minis current z-order
+            oldz = self.sel_rmin.zorder
+	    ##  print "old z-order = " + str(oldz)
+            self.sel_rmin.zorder += 1
+
+            #  Re-collapse to normalize
+            #  Note:  only one update (with the final values) will be sent
+            self.canvas.layers['miniatures'].collapse_zorder()
+
+        elif id == MIN_TO_FRONT:
+            #  This assumes that we always start out with a z-order
+            #     that starts at 0 and goes up to the number of
+            #     minis - 1.  If this isn't the case, then execute
+            #     a self.canvas.layers['miniatures'].collapse_zorder()
+            #     before getting the oldz to test
+            #  Save the selected minis current z-order
+            oldz = self.sel_rmin.zorder
+
+            # Make sure the mini isn't sticky front or back
+            if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
+	    ##  print "old z-order = " + str(oldz)
+                #  The new z-order will be one more than the last index
+                newz = len(self.canvas.layers['miniatures'].miniatures)
+	    ##  print "new z-order = " + str(newz)
+                self.sel_rmin.zorder = newz
+                #  Re-collapse to normalize
+                #  Note:  only one update (with the final values) will be sent
+                self.canvas.layers['miniatures'].collapse_zorder()
+
+        elif id == MIN_TO_BACK:
+            #  This assumes that we always start out with a z-order
+            #     that starts at 0 and goes up to the number of
+            #     minis - 1.  If this isn't the case, then execute
+            #     a self.canvas.layers['miniatures'].collapse_zorder()
+            #     before getting the oldz to test
+            #  Save the selected minis current z-order
+            oldz = self.sel_rmin.zorder
+            # Make sure the mini isn't sticky front or back
+            if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
+	    ##  print "old z-order = " + str(oldz)
+
+                #  Since 0 is the lowest in a normalized order, be one less
+                newz = -1
+	    ##  print "new z-order = " + str(newz)
+                self.sel_rmin.zorder = newz
+                #  Re-collapse to normalize
+                #  Note:  only one update (with the final values) will be sent
+                self.canvas.layers['miniatures'].collapse_zorder()
+
+        elif id == MIN_FRONTBACK_UNLOCK:
+            #print "Unlocked/ unstickified..."
+            if self.sel_rmin.zorder == MIN_STICKY_BACK:
+                self.sel_rmin.zorder = MIN_STICKY_BACK + 1
+            elif self.sel_rmin.zorder == MIN_STICKY_FRONT:
+                self.sel_rmin.zorder = MIN_STICKY_FRONT - 1
+        elif id == MIN_LOCK_BACK:
+            #print "lock back"
+            self.sel_rmin.zorder = MIN_STICKY_BACK
+        elif id == MIN_LOCK_FRONT:
+            #print "lock front"
+            self.sel_rmin.zorder = MIN_STICKY_FRONT
+        # Pretty much, we always want to refresh when we go through here
+        # This helps us remove the redundant self.Refresh() on EVERY menu event
+        # that we process above.
+        self.sel_rmin.isUpdated = True
+        self.canvas.Refresh(False)
+        self.canvas.send_map_data()
+
+    def on_miniature(self, evt):
+        session = self.canvas.frame.session
+        if (session.my_role() != session.ROLE_GM) and (session.my_role() != session.ROLE_PLAYER) and (session.use_roles()):
+            self.infoPost("You must be either a player or GM to use the miniature Layer")
+            return
+        min_url = self.min_url.GetValue()
+        # build url
+        if min_url == "" or min_url == "http://":
+            return
+        if min_url[:7] != "http://" :
+            min_url = "http://" + min_url
+        # make label
+        if self.auto_label and min_url[-4:-3] == '.':
+            start = min_url.rfind("/") + 1
+            min_label = min_url[start:len(min_url)-4]
+            if self.use_serial:
+                min_label = '%s %d' % ( min_label, self.canvas.layers['miniatures'].next_serial() )
+        else:
+            min_label = ""
+        if self.min_url.FindString(min_url) == -1:
+            self.min_url.Append(min_url)
+        try:
+            id = 'mini-' + self.canvas.frame.session.get_next_id()
+            # make the new mini appear in top left of current viewable map
+            dc = wx.ClientDC(self.canvas)
+            self.canvas.PrepareDC(dc)
+            dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+            x = dc.DeviceToLogicalX(0)
+            y = dc.DeviceToLogicalY(0)
+            self.canvas.layers['miniatures'].add_miniature(id, min_url, pos=cmpPoint(x,y), label=min_label)
+        except:
+            # When there is an exception here, we should be decrementing the serial_number for reuse!!
+            unablemsg= "Unable to load/resolve URL: " + min_url + " on resource \"" + min_label + "\"!!!\n\n"
+            #print unablemsg
+            dlg = wx.MessageDialog(self,unablemsg, 'Url not found',wx.ICON_EXCLAMATION)
+            dlg.ShowModal()
+            dlg.Destroy()
+            self.canvas.layers['miniatures'].rollback_serial()
+        self.canvas.send_map_data()
+        self.canvas.Refresh(False)
+        #except Exception, e:
+            #wx.MessageBox(str(e),"Miniature Error")
+
+    def on_label(self,evt):
+        self.auto_label = not self.auto_label
+        self.auto_label_cb.SetValue(self.auto_label)
+        #self.send_map_data()
+        #self.Refresh()
+
+    def on_min_list(self,evt):
+        session = self.canvas.frame.session
+        if (session.my_role() != session.ROLE_GM):
+            self.infoPost("You must be a GM to use this feature")
+            return
+        #d = min_list_panel(self.frame.GetParent(),self.canvas.layers,"Miniature list")
+        d = min_list_panel(self.canvas.frame,self.canvas.layers,"Miniature list")
+        if d.ShowModal() == wx.ID_OK:
+            d.Destroy()
+        self.canvas.Refresh(False)
+
+    def on_serial(self, evt):
+        self.use_serial = not self.use_serial
+
+    def on_map_board_menu_item(self,evt):
+        id = evt.GetId()
+        if id == MAP_REFRESH_MINI_URLS:   # Note: this doesn't change the mini, so no need to update the map
+            for mini in self.canvas.layers['miniatures'].miniatures:       #  For all minis
+                mini.set_bmp(ImageHandler.load(mini.path, 'miniature', mini.id))      #  Reload their bmp member
+            self.canvas.Refresh(False)
+
+####################################################################
+    ## old functions, changed an awful lot
+
+    def on_left_down(self, evt):
+        if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
+            return
+        mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
+        if mini:
+            deselecting_selected_mini = (mini == self.sel_min) #clicked on the selected mini
+            self.deselectAndRefresh()
+            self.drag_mini = mini
+            if deselecting_selected_mini:
+                return
+            self.sel_min = mini
+            self.sel_min.selected = True
+            dc = wx.ClientDC(self.canvas)
+            self.canvas.PrepareDC(dc)
+            dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+            self.sel_min.draw(dc, self.canvas.layers['miniatures'])
+        else:
+            self.drag_mini = None
+            pos = self.getLogicalPosition(evt)
+            self.moveSelectedMini(pos)
+            self.deselectAndRefresh()
+
+    def on_right_down(self, evt):
+        if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
+            return
+        self.last_rclick_pos = self.getLogicalPosition(evt)
+        mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
+        if mini:
+            self.sel_rmin = mini
+            if self.sel_min:
+                self.min_menu.Enable(MIN_MOVE, True)
+            else:
+                self.min_menu.Enable(MIN_MOVE, False)
+            self.prepare_mini_rclick_menu(evt)
+            self.do_min_menu(evt.GetPosition())
+        else:# pass it on
+            if self.sel_min:
+                self.main_menu.Enable(MIN_MOVE, True)
+            else:
+                self.main_menu.Enable(MIN_MOVE, False)
+            self.prepare_background_rclick_menu(evt)
+            base_layer_handler.on_right_down(self, evt)
+
+####################################################################
+    ## new functions
+
+    def on_drop_files(self, x, y, filepaths):
+        # currently we ignore multiple files
+        filepath = filepaths[0]
+        start1 = filepath.rfind("\\") + 1 # check for both slashes in path to be on the safe side
+        start2 = filepath.rfind("/") + 1
+        if start1 < start2:
+            start1 = start2
+        filename = filepath[start1:]
+        pos = filename.rfind('.')
+        ext = filename[pos:].lower()
+   	# ext = filename[-4:].lower()
+        if(ext != ".bmp" and ext != ".gif" and ext != ".jpg" and ext != ".jpeg" and ext != ".png"):
+            self.infoPost("Supported file extensions are: *.bmp, *.gif, *.jpg, *.jpeg, *.png")
+            return
+        file = open(filepath, "rb")
+        imgdata = file.read()
+        file.close()
+        dc = wx.ClientDC(self.canvas)
+        self.canvas.PrepareDC(dc)
+        dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+        x = dc.DeviceToLogicalX(x)
+        y = dc.DeviceToLogicalY(y)
+        (imgtype,j) = mimetypes.guess_type(filename)
+        postdata = urllib.urlencode({'filename':filename, 'imgdata':imgdata, 'imgtype':imgtype})
+        thread.start_new_thread(self.canvas.layers['miniatures'].upload, (postdata, filepath), {'pos':cmpPoint(x,y)})
+
+    def on_tooltip_timer(self, *args):
+        pos = args[0]
+        dc = wx.ClientDC(self.canvas)
+        self.canvas.PrepareDC(dc)
+        dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+        pos = wx.Point(dc.DeviceToLogicalX(pos.x), dc.DeviceToLogicalY(pos.y))
+        mini_list = self.getMiniListOrSelectedMini(pos)
+        if len(mini_list) > 0:
+            tooltip = self.get_mini_tooltip(mini_list)
+            self.canvas.SetToolTipString(tooltip)
+        else:
+            self.canvas.SetToolTipString("")
+
+    def on_motion(self,evt):
+        if evt.Dragging() and evt.LeftIsDown():
+            if self.canvas.drag is None and self.drag_mini is not None:
+                drag_bmp = self.drag_mini.bmp
+                if self.drag_mini.width and self.drag_mini.height:
+                    tmp_image = drag_bmp.ConvertToImage()
+                    tmp_image.Rescale(int(self.drag_mini.width * self.canvas.layers['grid'].mapscale), int(self.drag_mini.height * self.canvas.layers['grid'].mapscale))
+                    tmp_image.ConvertAlphaToMask()
+                    drag_bmp = tmp_image.ConvertToBitmap()
+                    mask = wx.Mask(drag_bmp, wx.Colour(tmp_image.GetMaskRed(), tmp_image.GetMaskGreen(), tmp_image.GetMaskBlue()))
+                    drag_bmp.SetMask(mask)
+                    tmp_image = tmp_image.ConvertToGreyscale()
+                    self.drag_mini.gray = True
+                    self.drag_mini.isUpdated = True
+                    def refresh():
+                        self.canvas.drag.Hide()
+                        self.canvas.Refresh(False)
+                    wx.CallAfter(refresh)
+                self.canvas.drag = wx.DragImage(drag_bmp)
+                self.drag_offset = self.getLogicalPosition(evt)- self.drag_mini.pos
+                self.canvas.drag.BeginDrag((int(self.drag_offset.x * self.canvas.layers['grid'].mapscale), int(self.drag_offset.y * self.canvas.layers['grid'].mapscale)), self.canvas, False)
+            elif self.canvas.drag is not None:
+                self.canvas.drag.Move(evt.GetPosition())
+                self.canvas.drag.Show()
+        # reset tool tip timer
+        self.canvas.SetToolTipString("")
+        self.tooltip_timer.Restart(self.tooltip_delay_miliseconds, evt.GetPosition())
+
+    def on_left_up(self,evt):
+        if self.canvas.drag:
+            self.canvas.drag.Hide()
+            self.canvas.drag.EndDrag()
+            self.canvas.drag = None
+            pos = self.getLogicalPosition(evt)
+            pos = pos - self.drag_offset
+            if self.canvas.layers['grid'].snap:
+                nudge = int(self.canvas.layers['grid'].unit_size/2)
+                if self.canvas.layers['grid'].mode != GRID_ISOMETRIC:
+                    if self.drag_mini.snap_to_align == SNAPTO_ALIGN_CENTER:
+                        pos = pos + (int(self.drag_mini.bmp.GetWidth()/2),int(self.drag_mini.bmp.GetHeight()/2))
+                    else:
+                        pos = pos + (nudge, nudge)
+                else:# GRID_ISOMETRIC
+                    if self.drag_mini.snap_to_align == SNAPTO_ALIGN_CENTER:
+                        pos = pos + (int(self.drag_mini.bmp.GetWidth()/2), self.drag_mini.bmp.GetHeight())
+                    else:
+                        pass # no nudge for the isomorphic / top-left
+            self.sel_min = self.drag_mini
+            # check to see if the mouse is inside the window still
+            w = self.canvas.GetClientSizeTuple() # this is the window size, minus any scrollbars
+            p = evt.GetPosition() # compare the window size, w with the non-logical position
+            c = self.canvas.size # this is the grid size, compare with the logical position, pos
+            # both are [width, height]
+            if p.x>=0 and pos.x<c[0] and p.x<w[0] and p.y>=0 and pos.y<c[1] and p.y<w[1]:
+                self.moveSelectedMini(pos)
+            self.sel_min.gray = False
+            self.sel_min.selected = False
+            self.sel_min.isUpdated = True
+            self.canvas.Refresh(False)
+            self.canvas.send_map_data()
+            self.sel_min = None
+        self.drag_mini = None
+
+    def on_left_dclick(self,evt):
+        if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
+            return
+        mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
+        if mini:
+            self.on_mini_dclick(evt, mini)
+        else:# pass it on
+            base_layer_handler.on_left_dclick(self, evt)
+
+
+####################################################################
+    ## hook functions (although with python you can override any of the functions)
+
+    def prepare_mini_rclick_menu(self, evt):
+        # override the entire right-click on a mini menu
+        pass
+
+    def prepare_background_rclick_menu(self, evt):
+        # override the entire right-click on the map menu
+        pass
+
+    def get_mini_tooltip(self, mini_list):
+        # override to create a tooltip
+        return ""
+
+    def on_mini_dclick(self, evt, mini):
+        # do something after the mini was left double clicked
+        pass
+
+####################################################################
+    ## easy way to add a single menu item
+
+    def set_mini_rclick_menu_item(self, label, callback_function):
+        # remember you might want to call these at the end of your callback function:
+        # mini_handler.sel_rmin.isUpdated = True
+        # canvas.Refresh(False)
+        # canvas.send_map_data()
+        if callback_function == None:
+            del self.mini_rclick_menu_extra_items[label]
+        else:
+            if not self.mini_rclick_menu_extra_items.has_key(label):
+                self.mini_rclick_menu_extra_items[label]=wx.NewId()
+            menu_id = self.mini_rclick_menu_extra_items[label]
+            self.canvas.Bind(wx.EVT_MENU, callback_function, id=menu_id)
+        self.build_menu()
+
+    def set_background_rclick_menu_item(self, label, callback_function):
+        if callback_function == None:
+            del self.background_rclick_menu_extra_items[label]
+        else:
+            if not self.background_rclick_menu_extra_items.has_key(label):
+                self.background_rclick_menu_extra_items[label]=wx.NewId()
+            menu_id = self.background_rclick_menu_extra_items[label]
+            self.canvas.Bind(wx.EVT_MENU, callback_function, id=menu_id)
+        self.build_menu()
+
+
+####################################################################
+    ## helper functions
+
+    def infoPost(self, message):
+        open_rpg.get_component("chat").InfoPost(message)
+
+    def role_is_gm_or_player(self):
+        session = self.canvas.frame.session
+        if (session.my_role() <> session.ROLE_GM) and (session.my_role() <> session.ROLE_PLAYER) and (session.use_roles()):
+            self.infoPost("You must be either a player or GM to use the miniature Layer")
+            return False
+        return True
+
+    def role_is_gm(self):
+        session = self.canvas.frame.session
+        if (session.my_role() <> session.ROLE_GM) and (session.use_roles()):
+            return False
+        return True
+
+    def alreadyDealingWithMenu(self):
+        return self.lastMenuChoice is not None
+
+    def getLastMenuChoice(self):
+        choice = self.lastMenuChoice
+        self.lastMenuChoice = None
+        return choice
+
+    def getLogicalPosition(self, evt):
+        dc = wx.ClientDC(self.canvas)
+        self.canvas.PrepareDC(dc)
+        dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
+        pos = evt.GetLogicalPosition(dc)
+        return pos
+
+    def getMiniListOrSelectedMini(self, pos, include_locked=False):
+        if self.sel_min and self.sel_min.hit_test(pos):
+            # clicked on the selected mini - assume that is the intended target
+            # and don't give a choice of it and any other minis stacked with it
+            mini_list = []
+            mini_list.append(self.sel_min)
+            return mini_list
+        mini_list = self.canvas.layers['miniatures'].find_miniature(pos, (not include_locked))
+        if mini_list:
+            return mini_list
+        mini_list = []
+        return mini_list
+
+    def deselectAndRefresh(self):
+        if self.sel_min:
+            self.sel_min.selected = False
+            self.sel_min.isUpdated = True
+            self.canvas.Refresh(False)
+            self.canvas.send_map_data()
+            self.sel_min = None
+
+    def moveSelectedMini(self, pos):
+        if self.sel_min:
+            self.moveMini(pos, self.sel_min)
+
+    def moveMini(self, pos, mini):
+        grid = self.canvas.layers['grid']
+        mini.pos = grid.get_snapped_to_pos(pos, mini.snap_to_align, mini.bmp.GetWidth(), mini.bmp.GetHeight())
+
+    def find_mini(self, evt, include_locked):
+        if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
+            return
+        pos = self.getLogicalPosition(evt)
+        mini_list = self.getMiniListOrSelectedMini(pos, include_locked)
+        mini = None
+        if len(mini_list) > 1:
+            try:
+                self.do_min_select_menu(mini_list, evt.GetPosition())
+            except:
+                pass
+            choice = self.getLastMenuChoice()
+            if choice == None:
+                return None # left menu without making a choice, eg by clicking outside menu
+            mini = mini_list[choice]
+        elif len(mini_list) == 1:
+            mini = mini_list[0]
+        return mini
+