comparison plugins/bcg/token_handler.py @ 105:2f2bebe9c77f alpha

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