comparison 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 2b9e766f9dee
comparison
equal deleted inserted replaced
-1:000000000000 0:4385a7d0efd1
1 # Copyright (C) 2000-2001 The OpenRPG Project
2 #
3 # openrpg-dev@lists.sourceforge.net
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 # --
19 #
20 # File: mapper/whiteboard_hander.py
21 # Author: OpenRPG Team
22 # Maintainer:
23 # Version:
24 # $Id: miniatures_handler.py,v 1.43 2007/12/07 20:39:50 digitalxero Exp $
25 #
26 # Description: Miniature layer handler
27 #
28 __version__ = "$Id: miniatures_handler.py,v 1.43 2007/12/07 20:39:50 digitalxero Exp $"
29
30 from base_handler import *
31 from min_dialogs import *
32 import thread
33 import time
34 import mimetypes
35 import urllib
36 import xml.dom.minidom as minidom
37 import wx
38 from grid import GRID_RECTANGLE
39 from grid import GRID_HEXAGON
40 from grid import GRID_ISOMETRIC
41 LABEL_TOOL = wx.NewId()
42 LAYER_TOOL = wx.NewId()
43 MIN_LIST_TOOL = wx.NewId()
44 MIN_TOOL = wx.NewId()
45 MIN_URL = wx.NewId()
46 SERIAL_TOOL = wx.NewId()
47 MIN_MOVE = wx.NewId()
48 MIN_REMOVE = wx.NewId()
49 MIN_PROP_DLG = wx.NewId()
50 MIN_FACING_NONE = wx.NewId()
51 MIN_FACING_MATCH = wx.NewId()
52 MIN_FACING_EAST = wx.NewId()
53 MIN_FACING_WEST = wx.NewId()
54 MIN_FACING_NORTH = wx.NewId()
55 MIN_FACING_SOUTH = wx.NewId()
56 MIN_FACING_NORTHEAST = wx.NewId()
57 MIN_FACING_SOUTHEAST = wx.NewId()
58 MIN_FACING_SOUTHWEST = wx.NewId()
59 MIN_FACING_NORTHWEST = wx.NewId()
60 MIN_HEADING_NONE = wx.NewId()
61 MIN_HEADING_MATCH = wx.NewId()
62 MIN_HEADING_EAST = wx.NewId()
63 MIN_HEADING_WEST = wx.NewId()
64 MIN_HEADING_NORTH = wx.NewId()
65 MIN_HEADING_SOUTH = wx.NewId()
66 MIN_HEADING_NORTHEAST = wx.NewId()
67 MIN_HEADING_SOUTHEAST = wx.NewId()
68 MIN_HEADING_SOUTHWEST = wx.NewId()
69 MIN_HEADING_NORTHWEST = wx.NewId()
70 MIN_HEADING_SUBMENU = wx.NewId()
71 MIN_FACING_SUBMENU = wx.NewId()
72 MIN_ALIGN_SUBMENU = wx.NewId()
73 MIN_ALIGN_GRID_CENTER = wx.NewId()
74 MIN_ALIGN_GRID_TL = wx.NewId()
75 MIN_TITLE_HACK = wx.NewId()
76 MIN_TO_GAMETREE = wx.NewId()
77 MIN_BACK_ONE = wx.NewId()
78 MIN_FORWARD_ONE = wx.NewId()
79 MIN_TO_BACK = wx.NewId()
80 MIN_TO_FRONT = wx.NewId()
81 MIN_LOCK_BACK = wx.NewId()
82 MIN_LOCK_FRONT = wx.NewId()
83 MIN_FRONTBACK_UNLOCK = wx.NewId()
84 MIN_ZORDER_SUBMENU = wx.NewId()
85 MIN_SHOW_HIDE = wx.NewId()
86 MIN_LOCK_UNLOCK = wx.NewId()
87 MAP_REFRESH_MINI_URLS = wx.NewId()
88
89 class myFileDropTarget(wx.FileDropTarget):
90 def __init__(self, handler):
91 wx.FileDropTarget.__init__(self)
92 self.m_handler = handler
93 def OnDropFiles(self, x, y, filenames):
94 self.m_handler.on_drop_files(x, y, filenames)
95
96 class miniatures_handler(base_layer_handler):
97
98 def __init__(self, parent, id, canvas):
99 self.sel_min = None
100 self.auto_label = 1
101 self.use_serial = 1
102 self.auto_label_cb = None
103 self.canvas = canvas
104 self.settings = self.canvas.settings
105 self.mini_rclick_menu_extra_items = {}
106 self.background_rclick_menu_extra_items = {}
107 base_layer_handler.__init__(self, parent, id, canvas)
108 # id is the index of the last good menu choice or 'None'
109 # if the last menu was left without making a choice
110 # should be -1 at other times to prevent events overlapping
111 self.lastMenuChoice = None
112 self.drag_mini = None
113 self.tooltip_delay_miliseconds = 500
114 self.tooltip_timer = wx.CallLater(self.tooltip_delay_miliseconds, self.on_tooltip_timer)
115 self.tooltip_timer.Stop()
116 dt = myFileDropTarget(self)
117 self.canvas.SetDropTarget(dt)
118 # wxInitAllImageHandlers()
119
120 def build_ctrls(self):
121 base_layer_handler.build_ctrls(self)
122 # add controls in reverse order! (unless you want them after the default tools)
123 self.auto_label_cb = wx.CheckBox(self, wx.ID_ANY, ' Auto Label ', (-1,-1),(-1,-1))
124 self.auto_label_cb.SetValue(self.auto_label)
125 self.min_url = wx.ComboBox(self, wx.ID_ANY, "http://", style=wx.CB_DROPDOWN | wx.CB_SORT)
126 self.localBrowse = wx.Button(self, wx.ID_ANY, 'Browse')
127 minilist = createMaskedButton( self, orpg.dirpath.dir_struct["icon"]+'questionhead.gif', 'Edit miniature properties', wx.ID_ANY)
128 miniadd = wx.Button(self, wx.ID_OK, "Add Miniature", style=wx.BU_EXACTFIT)
129 self.sizer.Add(self.auto_label_cb,0,wx.ALIGN_CENTER)
130 self.sizer.Add(self.min_url, 1, wx.ALIGN_CENTER)
131 self.sizer.Add(miniadd, 0, wx.EXPAND)
132 self.sizer.Add(self.localBrowse, 0, wx.EXPAND)
133 self.sizer.Add(wx.Size(20,25))
134 self.sizer.Add(minilist, 0, wx.EXPAND )
135 self.Bind(wx.EVT_BUTTON, self.on_min_list, minilist)
136 self.Bind(wx.EVT_BUTTON, self.on_miniature, miniadd)
137 self.Bind(wx.EVT_BUTTON, self.on_browse, self.localBrowse)
138 self.Bind(wx.EVT_CHECKBOX, self.on_label, self.auto_label_cb)
139
140 def on_browse(self, evt):
141 if not self.role_is_gm_or_player():
142 return
143 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)
144 if not dlg.ShowModal() == wx.ID_OK:
145 dlg.Destroy()
146 return
147 file = open(dlg.GetPath(), "rb")
148 imgdata = file.read()
149 file.close()
150 filename = dlg.GetFilename()
151 (imgtype,j) = mimetypes.guess_type(filename)
152 postdata = urllib.urlencode({'filename':filename, 'imgdata':imgdata, 'imgtype':imgtype})
153 if self.settings.get_setting('LocalorRemote') == 'Remote':
154 # make the new mini appear in top left of current viewable map
155 dc = wx.ClientDC(self.canvas)
156 self.canvas.PrepareDC(dc)
157 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
158 x = dc.DeviceToLogicalX(0)
159 y = dc.DeviceToLogicalY(0)
160 thread.start_new_thread(self.canvas.layers['miniatures'].upload, (postdata, dlg.GetPath()), {'pos':cmpPoint(x,y)})
161 else:
162 min_url = self.settings.get_setting('LocalImageBaseURL') + filename
163 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
164 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
165 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
166 # build url
167 if min_url == "" or min_url == "http://":
168 return
169 if min_url[:7] != "http://" :
170 min_url = "http://" + min_url
171 # make label
172 if self.auto_label and min_url[-4:-3] == '.':
173 start = min_url.rfind("/") + 1
174 min_label = min_url[start:len(min_url)-4]
175 if self.use_serial:
176 min_label = '%s %d' % ( min_label, self.canvas.layers['miniatures'].next_serial() )
177 else:
178 min_label = ""
179 if self.min_url.FindString(min_url) == -1:
180 self.min_url.Append(min_url)
181 try:
182 id = 'mini-' + self.canvas.frame.session.get_next_id()
183 # make the new mini appear in top left of current viewable map
184 dc = wx.ClientDC(self.canvas)
185 self.canvas.PrepareDC(dc)
186 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
187 x = dc.DeviceToLogicalX(0)
188 y = dc.DeviceToLogicalY(0)
189 self.canvas.layers['miniatures'].add_miniature(id, min_url, pos=cmpPoint(x,y), label=min_label)
190 except:
191 # When there is an exception here, we should be decrementing the serial_number for reuse!!
192 unablemsg= "Unable to load/resolve URL: " + min_url + " on resource \"" + min_label + "\"!!!\n\n"
193 #print unablemsg
194 dlg = wx.MessageDialog(self,unablemsg, 'Url not found',wx.ICON_EXCLAMATION)
195 dlg.ShowModal()
196 dlg.Destroy()
197 self.canvas.layers['miniatures'].rollback_serial()
198 self.canvas.send_map_data()
199 self.canvas.Refresh(False)
200
201
202 def build_menu(self,label = "Miniature"):
203 base_layer_handler.build_menu(self,label)
204 self.main_menu.AppendSeparator()
205 self.main_menu.Append(LABEL_TOOL,"&Auto label","",1)
206 self.main_menu.Check(LABEL_TOOL,self.auto_label)
207 self.main_menu.Append(SERIAL_TOOL,"&Number minis","",1)
208 self.main_menu.Check(SERIAL_TOOL, self.use_serial)
209 self.main_menu.Append(MAP_REFRESH_MINI_URLS,"&Refresh miniatures") # Add the menu item
210 self.main_menu.AppendSeparator()
211 self.main_menu.Append(MIN_MOVE, "Move")
212 self.canvas.Bind(wx.EVT_MENU, self.on_map_board_menu_item, id=MAP_REFRESH_MINI_URLS) # Set the handler
213 self.canvas.Bind(wx.EVT_MENU, self.on_label, id=LABEL_TOOL)
214 self.canvas.Bind(wx.EVT_MENU, self.on_serial, id=SERIAL_TOOL)
215 # build miniature meenu
216 self.min_menu = wx.Menu()
217 # Rectangles and hexagons require slightly different menus because of
218 # facing and heading possibilities.
219 heading_menu = wx.Menu()
220 face_menu = wx.Menu()
221 face_menu.Append(MIN_FACING_NONE,"&None")
222 face_menu.Append(MIN_FACING_NORTH,"&North")
223 face_menu.Append(MIN_FACING_NORTHEAST,"Northeast")
224 face_menu.Append(MIN_FACING_EAST,"East")
225 face_menu.Append(MIN_FACING_SOUTHEAST,"Southeast")
226 face_menu.Append(MIN_FACING_SOUTH,"&South")
227 face_menu.Append(MIN_FACING_SOUTHWEST,"Southwest")
228 face_menu.Append(MIN_FACING_WEST,"West")
229 face_menu.Append(MIN_FACING_NORTHWEST,"Northwest")
230 heading_menu.Append(MIN_HEADING_NONE,"&None")
231 heading_menu.Append(MIN_HEADING_NORTH,"&North")
232 heading_menu.Append(MIN_HEADING_NORTHEAST,"Northeast")
233 heading_menu.Append(MIN_HEADING_EAST,"East")
234 heading_menu.Append(MIN_HEADING_SOUTHEAST,"Southeast")
235 heading_menu.Append(MIN_HEADING_SOUTH,"&South")
236 heading_menu.Append(MIN_HEADING_SOUTHWEST,"Southwest")
237 heading_menu.Append(MIN_HEADING_WEST,"West")
238 heading_menu.Append(MIN_HEADING_NORTHWEST,"Northwest")
239 align_menu = wx.Menu()
240 align_menu.Append(MIN_ALIGN_GRID_CENTER,"&Center")
241 align_menu.Append(MIN_ALIGN_GRID_TL,"&Top-Left")
242 # This is a hack to simulate a menu title, due to problem in Linux
243 if wx.Platform == '__WXMSW__':
244 self.min_menu.SetTitle(label)
245 else:
246 self.min_menu.Append(MIN_TITLE_HACK,label)
247 self.min_menu.AppendSeparator()
248 self.min_menu.Append(MIN_SHOW_HIDE,"Show / Hide")
249 self.min_menu.Append(MIN_LOCK_UNLOCK, "Lock / Unlock")
250 self.min_menu.Append(MIN_REMOVE,"&Remove")
251 self.min_menu.Append(MIN_TO_GAMETREE,"To &Gametree")
252 self.min_menu.AppendMenu(MIN_HEADING_SUBMENU,"Set &Heading",heading_menu)
253 self.min_menu.AppendMenu(MIN_FACING_SUBMENU,"Set &Facing",face_menu)
254 self.min_menu.AppendMenu(MIN_ALIGN_SUBMENU,"Snap-to &Alignment",align_menu)
255 self.min_menu.AppendSeparator()
256 zorder_menu = wx.Menu()
257 zorder_menu.Append(MIN_BACK_ONE,"Back one")
258 zorder_menu.Append(MIN_FORWARD_ONE,"Forward one")
259 zorder_menu.Append(MIN_TO_BACK,"To back")
260 zorder_menu.Append(MIN_TO_FRONT,"To front")
261 zorder_menu.AppendSeparator()
262 zorder_menu.Append(MIN_LOCK_BACK,"Lock to back")
263 zorder_menu.Append(MIN_LOCK_FRONT,"Lock to front")
264 zorder_menu.Append(MIN_FRONTBACK_UNLOCK,"Unlock Front/Back")
265 self.min_menu.AppendMenu(MIN_ZORDER_SUBMENU, "Miniature Z-Order",zorder_menu)
266 #self.min_menu.Append(MIN_LOCK,"&Lock")
267 self.min_menu.AppendSeparator()
268 self.min_menu.Append(MIN_PROP_DLG,"&Properties")
269 self.min_menu.AppendSeparator()
270 self.min_menu.Append(MIN_MOVE, "Move")
271 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_MOVE)
272 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_SHOW_HIDE)
273 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_UNLOCK)
274 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_REMOVE)
275 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_GAMETREE)
276 #self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK)
277 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_PROP_DLG)
278 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NONE)
279 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_EAST)
280 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_WEST)
281 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTH)
282 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTH)
283 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTHEAST)
284 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTHEAST)
285 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_SOUTHWEST)
286 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FACING_NORTHWEST)
287 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NONE)
288 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_EAST)
289 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_WEST)
290 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTH)
291 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTH)
292 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTHEAST)
293 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTHEAST)
294 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_SOUTHWEST)
295 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_HEADING_NORTHWEST)
296 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_ALIGN_GRID_CENTER)
297 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_ALIGN_GRID_TL)
298 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_BACK_ONE)
299 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FORWARD_ONE)
300 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_BACK)
301 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_TO_FRONT)
302 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_BACK)
303 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_LOCK_FRONT)
304 self.canvas.Bind(wx.EVT_MENU, self.on_min_menu_item, id=MIN_FRONTBACK_UNLOCK)
305 ######### add plugin added menu items #########
306 if len(self.mini_rclick_menu_extra_items)>0:
307 self.min_menu.AppendSeparator()
308 for item in self.mini_rclick_menu_extra_items.items():
309 self.min_menu.Append(item[1], item[0])
310 if len(self.background_rclick_menu_extra_items)>0:
311 self.main_menu.AppendSeparator()
312 for item in self.background_rclick_menu_extra_items.items():
313 self.main_menu.Append(item[1], item[0])
314
315 def do_min_menu(self,pos):
316 self.canvas.PopupMenu(self.min_menu,pos)
317
318 def do_min_select_menu(self, min_list, pos):
319 # to prevent another event being processed
320 self.lastMenuChoice = None
321 self.min_select_menu = wx.Menu()
322 self.min_select_menu.SetTitle("Select Miniature")
323 loop_count = 1
324 try:
325 for m in min_list:
326 # Either use the miniatures label for the selection list
327 if m.label:
328 self.min_select_menu.Append(loop_count, m.label)
329 # Or use part of the images filename as an identifier
330 else:
331 string_split = string.split(m.path,"/",)
332 last_string = string_split[len(string_split)-1]
333 self.min_select_menu.Append(loop_count, 'Unlabeled - ' + last_string[:len(last_string)-4])
334 self.canvas.Bind(wx.EVT_MENU, self.min_selected, id=loop_count)
335 loop_count += 1
336 self.canvas.PopupMenu(self.min_select_menu,pos)
337 except:
338 pass
339
340 def min_selected(self,evt):
341 # this is the callback function for the menu that is used to choose
342 # between minis when you right click, left click or left double click
343 # on a stack of two or more
344 self.canvas.Refresh(False)
345 self.canvas.send_map_data()
346 self.lastMenuChoice = evt.GetId()-1
347
348 def on_min_menu_item(self,evt):
349 id = evt.GetId()
350 if id == MIN_MOVE:
351 if self.sel_min:
352 self.moveSelectedMini(self.last_rclick_pos)
353 self.deselectAndRefresh()
354 return
355 elif id == MIN_REMOVE:
356 self.canvas.layers['miniatures'].del_miniature(self.sel_rmin)
357 elif id == MIN_TO_GAMETREE:
358 min_xml = self.sel_rmin.toxml(action="new")
359 node_begin = "<nodehandler module='map_miniature_nodehandler' class='map_miniature_handler' name='"
360 if self.sel_rmin.label:
361 node_begin += self.sel_rmin.label + "'"
362 else:
363 node_begin += "Unnamed Miniature'"
364 node_begin += ">"
365 gametree = open_rpg.get_component('tree')
366 node_xml = node_begin + min_xml + '</nodehandler>'
367 #print "Sending this XML to insert_xml:" + node_xml
368 gametree.insert_xml(node_xml)
369 elif id == MIN_SHOW_HIDE:
370 if self.sel_rmin.hide:
371 self.sel_rmin.hide = 0
372 else:
373 self.sel_rmin.hide = 1
374 elif id == MIN_LOCK_UNLOCK:
375 if self.sel_rmin.locked:
376 self.sel_rmin.locked = False
377 else:
378 self.sel_rmin.locked = True
379 if self.sel_rmin == self.sel_min:
380 # when we lock / unlock the selected mini make sure it isn't still selected
381 # or it might easily get moved by accident and be hard to move back
382 self.sel_min.selected = False
383 self.sel_min.isUpdated = True
384 self.sel_min = None
385 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}
386 if recycle_bin.has_key(id):
387 self.sel_rmin.heading = recycle_bin[id]
388 recycle_bin = {}
389 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}
390 if recycle_bin.has_key(id):
391 self.sel_rmin.face = recycle_bin[id]
392 recycle_bin = {}
393 elif id == MIN_ALIGN_GRID_CENTER:
394 self.sel_rmin.snap_to_align = SNAPTO_ALIGN_CENTER
395 elif id == MIN_ALIGN_GRID_TL:
396 self.sel_rmin.snap_to_align = SNAPTO_ALIGN_TL
397 elif id == MIN_PROP_DLG:
398 old_lock_value = self.sel_rmin.locked
399 dlg = min_edit_dialog(self.canvas.frame.GetParent(),self.sel_rmin)
400 if dlg.ShowModal() == wx.ID_OK:
401 if self.sel_rmin == self.sel_min and self.sel_rmin.locked != old_lock_value:
402 # when we lock / unlock the selected mini make sure it isn't still selected
403 # or it might easily get moved by accident and be hard to move back
404 self.sel_min.selected = False
405 self.sel_min.isUpdated = True
406 self.sel_min = None
407 self.canvas.Refresh(False)
408 self.canvas.send_map_data()
409 return
410
411 elif id == MIN_BACK_ONE:
412 # This assumes that we always start out with a z-order
413 # that starts at 0 and goes up to the number of
414 # minis - 1. If this isn't the case, then execute
415 # a self.canvas.layers['miniatures'].collapse_zorder()
416 # before getting the oldz to test
417 # Save the selected minis current z-order
418 oldz = self.sel_rmin.zorder
419 # Make sure the mini isn't sticky front or back
420 if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
421 ## print "old z-order = " + str(oldz)
422 self.sel_rmin.zorder -= 1
423 # Re-collapse to normalize
424 # Note: only one update (with the final values) will be sent
425 self.canvas.layers['miniatures'].collapse_zorder()
426
427 elif id == MIN_FORWARD_ONE:
428 # This assumes that we always start out with a z-order
429 # that starts at 0 and goes up to the number of
430 # minis - 1. If this isn't the case, then execute
431 # a self.canvas.layers['miniatures'].collapse_zorder()
432 # before getting the oldz to test
433 # Save the selected minis current z-order
434 oldz = self.sel_rmin.zorder
435 ## print "old z-order = " + str(oldz)
436 self.sel_rmin.zorder += 1
437
438 # Re-collapse to normalize
439 # Note: only one update (with the final values) will be sent
440 self.canvas.layers['miniatures'].collapse_zorder()
441
442 elif id == MIN_TO_FRONT:
443 # This assumes that we always start out with a z-order
444 # that starts at 0 and goes up to the number of
445 # minis - 1. If this isn't the case, then execute
446 # a self.canvas.layers['miniatures'].collapse_zorder()
447 # before getting the oldz to test
448 # Save the selected minis current z-order
449 oldz = self.sel_rmin.zorder
450
451 # Make sure the mini isn't sticky front or back
452 if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
453 ## print "old z-order = " + str(oldz)
454 # The new z-order will be one more than the last index
455 newz = len(self.canvas.layers['miniatures'].miniatures)
456 ## print "new z-order = " + str(newz)
457 self.sel_rmin.zorder = newz
458 # Re-collapse to normalize
459 # Note: only one update (with the final values) will be sent
460 self.canvas.layers['miniatures'].collapse_zorder()
461
462 elif id == MIN_TO_BACK:
463 # This assumes that we always start out with a z-order
464 # that starts at 0 and goes up to the number of
465 # minis - 1. If this isn't the case, then execute
466 # a self.canvas.layers['miniatures'].collapse_zorder()
467 # before getting the oldz to test
468 # Save the selected minis current z-order
469 oldz = self.sel_rmin.zorder
470 # Make sure the mini isn't sticky front or back
471 if (oldz != MIN_STICKY_BACK) and (oldz != MIN_STICKY_FRONT):
472 ## print "old z-order = " + str(oldz)
473
474 # Since 0 is the lowest in a normalized order, be one less
475 newz = -1
476 ## print "new z-order = " + str(newz)
477 self.sel_rmin.zorder = newz
478 # Re-collapse to normalize
479 # Note: only one update (with the final values) will be sent
480 self.canvas.layers['miniatures'].collapse_zorder()
481
482 elif id == MIN_FRONTBACK_UNLOCK:
483 #print "Unlocked/ unstickified..."
484 if self.sel_rmin.zorder == MIN_STICKY_BACK:
485 self.sel_rmin.zorder = MIN_STICKY_BACK + 1
486 elif self.sel_rmin.zorder == MIN_STICKY_FRONT:
487 self.sel_rmin.zorder = MIN_STICKY_FRONT - 1
488 elif id == MIN_LOCK_BACK:
489 #print "lock back"
490 self.sel_rmin.zorder = MIN_STICKY_BACK
491 elif id == MIN_LOCK_FRONT:
492 #print "lock front"
493 self.sel_rmin.zorder = MIN_STICKY_FRONT
494 # Pretty much, we always want to refresh when we go through here
495 # This helps us remove the redundant self.Refresh() on EVERY menu event
496 # that we process above.
497 self.sel_rmin.isUpdated = True
498 self.canvas.Refresh(False)
499 self.canvas.send_map_data()
500
501 def on_miniature(self, evt):
502 session = self.canvas.frame.session
503 if (session.my_role() != session.ROLE_GM) and (session.my_role() != session.ROLE_PLAYER) and (session.use_roles()):
504 self.infoPost("You must be either a player or GM to use the miniature Layer")
505 return
506 min_url = self.min_url.GetValue()
507 # build url
508 if min_url == "" or min_url == "http://":
509 return
510 if min_url[:7] != "http://" :
511 min_url = "http://" + min_url
512 # make label
513 if self.auto_label and min_url[-4:-3] == '.':
514 start = min_url.rfind("/") + 1
515 min_label = min_url[start:len(min_url)-4]
516 if self.use_serial:
517 min_label = '%s %d' % ( min_label, self.canvas.layers['miniatures'].next_serial() )
518 else:
519 min_label = ""
520 if self.min_url.FindString(min_url) == -1:
521 self.min_url.Append(min_url)
522 try:
523 id = 'mini-' + self.canvas.frame.session.get_next_id()
524 # make the new mini appear in top left of current viewable map
525 dc = wx.ClientDC(self.canvas)
526 self.canvas.PrepareDC(dc)
527 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
528 x = dc.DeviceToLogicalX(0)
529 y = dc.DeviceToLogicalY(0)
530 self.canvas.layers['miniatures'].add_miniature(id, min_url, pos=cmpPoint(x,y), label=min_label)
531 except:
532 # When there is an exception here, we should be decrementing the serial_number for reuse!!
533 unablemsg= "Unable to load/resolve URL: " + min_url + " on resource \"" + min_label + "\"!!!\n\n"
534 #print unablemsg
535 dlg = wx.MessageDialog(self,unablemsg, 'Url not found',wx.ICON_EXCLAMATION)
536 dlg.ShowModal()
537 dlg.Destroy()
538 self.canvas.layers['miniatures'].rollback_serial()
539 self.canvas.send_map_data()
540 self.canvas.Refresh(False)
541 #except Exception, e:
542 #wx.MessageBox(str(e),"Miniature Error")
543
544 def on_label(self,evt):
545 self.auto_label = not self.auto_label
546 self.auto_label_cb.SetValue(self.auto_label)
547 #self.send_map_data()
548 #self.Refresh()
549
550 def on_min_list(self,evt):
551 session = self.canvas.frame.session
552 if (session.my_role() != session.ROLE_GM):
553 self.infoPost("You must be a GM to use this feature")
554 return
555 #d = min_list_panel(self.frame.GetParent(),self.canvas.layers,"Miniature list")
556 d = min_list_panel(self.canvas.frame,self.canvas.layers,"Miniature list")
557 if d.ShowModal() == wx.ID_OK:
558 d.Destroy()
559 self.canvas.Refresh(False)
560
561 def on_serial(self, evt):
562 self.use_serial = not self.use_serial
563
564 def on_map_board_menu_item(self,evt):
565 id = evt.GetId()
566 if id == MAP_REFRESH_MINI_URLS: # Note: this doesn't change the mini, so no need to update the map
567 for mini in self.canvas.layers['miniatures'].miniatures: # For all minis
568 mini.set_bmp(ImageHandler.load(mini.path, 'miniature', mini.id)) # Reload their bmp member
569 self.canvas.Refresh(False)
570
571 ####################################################################
572 ## old functions, changed an awful lot
573
574 def on_left_down(self, evt):
575 if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
576 return
577 mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
578 if mini:
579 deselecting_selected_mini = (mini == self.sel_min) #clicked on the selected mini
580 self.deselectAndRefresh()
581 self.drag_mini = mini
582 if deselecting_selected_mini:
583 return
584 self.sel_min = mini
585 self.sel_min.selected = True
586 dc = wx.ClientDC(self.canvas)
587 self.canvas.PrepareDC(dc)
588 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
589 self.sel_min.draw(dc, self.canvas.layers['miniatures'])
590 else:
591 self.drag_mini = None
592 pos = self.getLogicalPosition(evt)
593 self.moveSelectedMini(pos)
594 self.deselectAndRefresh()
595
596 def on_right_down(self, evt):
597 if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
598 return
599 self.last_rclick_pos = self.getLogicalPosition(evt)
600 mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
601 if mini:
602 self.sel_rmin = mini
603 if self.sel_min:
604 self.min_menu.Enable(MIN_MOVE, True)
605 else:
606 self.min_menu.Enable(MIN_MOVE, False)
607 self.prepare_mini_rclick_menu(evt)
608 self.do_min_menu(evt.GetPosition())
609 else:# pass it on
610 if self.sel_min:
611 self.main_menu.Enable(MIN_MOVE, True)
612 else:
613 self.main_menu.Enable(MIN_MOVE, False)
614 self.prepare_background_rclick_menu(evt)
615 base_layer_handler.on_right_down(self, evt)
616
617 ####################################################################
618 ## new functions
619
620 def on_drop_files(self, x, y, filepaths):
621 # currently we ignore multiple files
622 filepath = filepaths[0]
623 start1 = filepath.rfind("\\") + 1 # check for both slashes in path to be on the safe side
624 start2 = filepath.rfind("/") + 1
625 if start1 < start2:
626 start1 = start2
627 filename = filepath[start1:]
628 pos = filename.rfind('.')
629 ext = filename[pos:].lower()
630 # ext = filename[-4:].lower()
631 if(ext != ".bmp" and ext != ".gif" and ext != ".jpg" and ext != ".jpeg" and ext != ".png"):
632 self.infoPost("Supported file extensions are: *.bmp, *.gif, *.jpg, *.jpeg, *.png")
633 return
634 file = open(filepath, "rb")
635 imgdata = file.read()
636 file.close()
637 dc = wx.ClientDC(self.canvas)
638 self.canvas.PrepareDC(dc)
639 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
640 x = dc.DeviceToLogicalX(x)
641 y = dc.DeviceToLogicalY(y)
642 (imgtype,j) = mimetypes.guess_type(filename)
643 postdata = urllib.urlencode({'filename':filename, 'imgdata':imgdata, 'imgtype':imgtype})
644 thread.start_new_thread(self.canvas.layers['miniatures'].upload, (postdata, filepath), {'pos':cmpPoint(x,y)})
645
646 def on_tooltip_timer(self, *args):
647 pos = args[0]
648 dc = wx.ClientDC(self.canvas)
649 self.canvas.PrepareDC(dc)
650 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
651 pos = wx.Point(dc.DeviceToLogicalX(pos.x), dc.DeviceToLogicalY(pos.y))
652 mini_list = self.getMiniListOrSelectedMini(pos)
653 if len(mini_list) > 0:
654 tooltip = self.get_mini_tooltip(mini_list)
655 self.canvas.SetToolTipString(tooltip)
656 else:
657 self.canvas.SetToolTipString("")
658
659 def on_motion(self,evt):
660 if evt.Dragging() and evt.LeftIsDown():
661 if self.canvas.drag is None and self.drag_mini is not None:
662 drag_bmp = self.drag_mini.bmp
663 if self.drag_mini.width and self.drag_mini.height:
664 tmp_image = drag_bmp.ConvertToImage()
665 tmp_image.Rescale(int(self.drag_mini.width * self.canvas.layers['grid'].mapscale), int(self.drag_mini.height * self.canvas.layers['grid'].mapscale))
666 tmp_image.ConvertAlphaToMask()
667 drag_bmp = tmp_image.ConvertToBitmap()
668 mask = wx.Mask(drag_bmp, wx.Colour(tmp_image.GetMaskRed(), tmp_image.GetMaskGreen(), tmp_image.GetMaskBlue()))
669 drag_bmp.SetMask(mask)
670 tmp_image = tmp_image.ConvertToGreyscale()
671 self.drag_mini.gray = True
672 self.drag_mini.isUpdated = True
673 def refresh():
674 self.canvas.drag.Hide()
675 self.canvas.Refresh(False)
676 wx.CallAfter(refresh)
677 self.canvas.drag = wx.DragImage(drag_bmp)
678 self.drag_offset = self.getLogicalPosition(evt)- self.drag_mini.pos
679 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)
680 elif self.canvas.drag is not None:
681 self.canvas.drag.Move(evt.GetPosition())
682 self.canvas.drag.Show()
683 # reset tool tip timer
684 self.canvas.SetToolTipString("")
685 self.tooltip_timer.Restart(self.tooltip_delay_miliseconds, evt.GetPosition())
686
687 def on_left_up(self,evt):
688 if self.canvas.drag:
689 self.canvas.drag.Hide()
690 self.canvas.drag.EndDrag()
691 self.canvas.drag = None
692 pos = self.getLogicalPosition(evt)
693 pos = pos - self.drag_offset
694 if self.canvas.layers['grid'].snap:
695 nudge = int(self.canvas.layers['grid'].unit_size/2)
696 if self.canvas.layers['grid'].mode != GRID_ISOMETRIC:
697 if self.drag_mini.snap_to_align == SNAPTO_ALIGN_CENTER:
698 pos = pos + (int(self.drag_mini.bmp.GetWidth()/2),int(self.drag_mini.bmp.GetHeight()/2))
699 else:
700 pos = pos + (nudge, nudge)
701 else:# GRID_ISOMETRIC
702 if self.drag_mini.snap_to_align == SNAPTO_ALIGN_CENTER:
703 pos = pos + (int(self.drag_mini.bmp.GetWidth()/2), self.drag_mini.bmp.GetHeight())
704 else:
705 pass # no nudge for the isomorphic / top-left
706 self.sel_min = self.drag_mini
707 # check to see if the mouse is inside the window still
708 w = self.canvas.GetClientSizeTuple() # this is the window size, minus any scrollbars
709 p = evt.GetPosition() # compare the window size, w with the non-logical position
710 c = self.canvas.size # this is the grid size, compare with the logical position, pos
711 # both are [width, height]
712 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]:
713 self.moveSelectedMini(pos)
714 self.sel_min.gray = False
715 self.sel_min.selected = False
716 self.sel_min.isUpdated = True
717 self.canvas.Refresh(False)
718 self.canvas.send_map_data()
719 self.sel_min = None
720 self.drag_mini = None
721
722 def on_left_dclick(self,evt):
723 if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
724 return
725 mini = self.find_mini(evt, evt.ControlDown() and self.role_is_gm())
726 if mini:
727 self.on_mini_dclick(evt, mini)
728 else:# pass it on
729 base_layer_handler.on_left_dclick(self, evt)
730
731
732 ####################################################################
733 ## hook functions (although with python you can override any of the functions)
734
735 def prepare_mini_rclick_menu(self, evt):
736 # override the entire right-click on a mini menu
737 pass
738
739 def prepare_background_rclick_menu(self, evt):
740 # override the entire right-click on the map menu
741 pass
742
743 def get_mini_tooltip(self, mini_list):
744 # override to create a tooltip
745 return ""
746
747 def on_mini_dclick(self, evt, mini):
748 # do something after the mini was left double clicked
749 pass
750
751 ####################################################################
752 ## easy way to add a single menu item
753
754 def set_mini_rclick_menu_item(self, label, callback_function):
755 # remember you might want to call these at the end of your callback function:
756 # mini_handler.sel_rmin.isUpdated = True
757 # canvas.Refresh(False)
758 # canvas.send_map_data()
759 if callback_function == None:
760 del self.mini_rclick_menu_extra_items[label]
761 else:
762 if not self.mini_rclick_menu_extra_items.has_key(label):
763 self.mini_rclick_menu_extra_items[label]=wx.NewId()
764 menu_id = self.mini_rclick_menu_extra_items[label]
765 self.canvas.Bind(wx.EVT_MENU, callback_function, id=menu_id)
766 self.build_menu()
767
768 def set_background_rclick_menu_item(self, label, callback_function):
769 if callback_function == None:
770 del self.background_rclick_menu_extra_items[label]
771 else:
772 if not self.background_rclick_menu_extra_items.has_key(label):
773 self.background_rclick_menu_extra_items[label]=wx.NewId()
774 menu_id = self.background_rclick_menu_extra_items[label]
775 self.canvas.Bind(wx.EVT_MENU, callback_function, id=menu_id)
776 self.build_menu()
777
778
779 ####################################################################
780 ## helper functions
781
782 def infoPost(self, message):
783 open_rpg.get_component("chat").InfoPost(message)
784
785 def role_is_gm_or_player(self):
786 session = self.canvas.frame.session
787 if (session.my_role() <> session.ROLE_GM) and (session.my_role() <> session.ROLE_PLAYER) and (session.use_roles()):
788 self.infoPost("You must be either a player or GM to use the miniature Layer")
789 return False
790 return True
791
792 def role_is_gm(self):
793 session = self.canvas.frame.session
794 if (session.my_role() <> session.ROLE_GM) and (session.use_roles()):
795 return False
796 return True
797
798 def alreadyDealingWithMenu(self):
799 return self.lastMenuChoice is not None
800
801 def getLastMenuChoice(self):
802 choice = self.lastMenuChoice
803 self.lastMenuChoice = None
804 return choice
805
806 def getLogicalPosition(self, evt):
807 dc = wx.ClientDC(self.canvas)
808 self.canvas.PrepareDC(dc)
809 dc.SetUserScale(self.canvas.layers['grid'].mapscale,self.canvas.layers['grid'].mapscale)
810 pos = evt.GetLogicalPosition(dc)
811 return pos
812
813 def getMiniListOrSelectedMini(self, pos, include_locked=False):
814 if self.sel_min and self.sel_min.hit_test(pos):
815 # clicked on the selected mini - assume that is the intended target
816 # and don't give a choice of it and any other minis stacked with it
817 mini_list = []
818 mini_list.append(self.sel_min)
819 return mini_list
820 mini_list = self.canvas.layers['miniatures'].find_miniature(pos, (not include_locked))
821 if mini_list:
822 return mini_list
823 mini_list = []
824 return mini_list
825
826 def deselectAndRefresh(self):
827 if self.sel_min:
828 self.sel_min.selected = False
829 self.sel_min.isUpdated = True
830 self.canvas.Refresh(False)
831 self.canvas.send_map_data()
832 self.sel_min = None
833
834 def moveSelectedMini(self, pos):
835 if self.sel_min:
836 self.moveMini(pos, self.sel_min)
837
838 def moveMini(self, pos, mini):
839 grid = self.canvas.layers['grid']
840 mini.pos = grid.get_snapped_to_pos(pos, mini.snap_to_align, mini.bmp.GetWidth(), mini.bmp.GetHeight())
841
842 def find_mini(self, evt, include_locked):
843 if not self.role_is_gm_or_player() or self.alreadyDealingWithMenu():
844 return
845 pos = self.getLogicalPosition(evt)
846 mini_list = self.getMiniListOrSelectedMini(pos, include_locked)
847 mini = None
848 if len(mini_list) > 1:
849 try:
850 self.do_min_select_menu(mini_list, evt.GetPosition())
851 except:
852 pass
853 choice = self.getLastMenuChoice()
854 if choice == None:
855 return None # left menu without making a choice, eg by clicking outside menu
856 mini = mini_list[choice]
857 elif len(mini_list) == 1:
858 mini = mini_list[0]
859 return mini
860