comparison clients/editor/plugins/mapeditor.py @ 0:4a0efb7baf70

* Datasets becomes the new trunk and retires after that :-)
author mvbarracuda@33b003aa-7bff-0310-803a-e67f0ece8222
date Sun, 29 Jun 2008 18:44:17 +0000
parents
children 9d0a21184c13
comparison
equal deleted inserted replaced
-1:000000000000 0:4a0efb7baf70
1 # MapEditor is a plugin for Fifedit. It allows for selection and visual editing of maps.
2 # MapEditor must be pumped (see pump).
3
4 import math
5
6 import fife
7 import plugin
8 import pychan
9 import pychan.widgets as widgets
10 from pychan.tools import callbackWithArguments as cbwa
11 from selection import Selection, ClickSelection
12 from plugins.objectselector import ObjectSelector
13
14 from pychan.manager import DEFAULT_STYLE
15 DEFAULT_STYLE['default']['base_color'] = fife.Color(85,128,151)
16
17 states = ('NOTHING_LOADED', 'VIEWING', 'INSERTING', 'REMOVING', 'MOVING')
18 for s in states:
19 globals()[s] = s
20 NOT_INITIALIZED = -9999999
21
22 class EditorLogicError(Exception):
23 pass
24
25 class MapSelection(object):
26 def __init__(self, onLayerSelect, onObjectSelect):
27 self._mapedit = None
28 self._onLayerSelect = onLayerSelect
29 self._onObjectSelect = onObjectSelect
30
31 def show(self, map):
32 if not self._mapedit:
33 self._mapedit = pychan.loadXML('content/gui/mapeditor.xml')
34 self._mapedit.mapEvents({
35 'layerButton' : self._onLayerSelect,
36 'objButton' : self._onObjectSelect,
37 'closeButton' : self.hide
38 })
39 fields = self._mapedit.findChild(name='Properties')
40 # Clear previously added children
41 fields.removeChildren(*fields.children)
42 hbox = widgets.HBox()
43 fields.addChild(hbox)
44 label = widgets.Label(text='ID',min_size=(80,0))
45 hbox.addChild(label)
46 field = widgets.TextField(text=map.getId(),min_size=(100,0))
47 hbox.addChild(field)
48 self._mapedit.adaptLayout()
49 self._mapedit.show()
50 self._mapedit.x = 10
51 self._mapedit.y = 580
52
53 def hide(self):
54 self._mapedit.hide()
55
56 class Toolbar(object):
57 def __init__(self, onSelect, onMove, onInsert, onDelete, onBtnEnter, onBtnExit):
58 self._onSelect, self._onMove, self._onInsert, self._onDelete = onSelect, onMove, onInsert, onDelete
59 self.onBtnEnter, self.onBtnExit = onBtnEnter, onBtnExit
60 self._toolbar = None
61
62 def show(self):
63 if not self._toolbar:
64 self._toolbar = pychan.loadXML('content/gui/tools.xml')
65 evtmap = {
66 'btnSelect' : self._onSelect,
67 'btnMove' : self._onMove,
68 'btnInsert' : self._onInsert,
69 'btnDelete' : self._onDelete
70 }
71 self._toolbar.mapEvents(evtmap)
72 for k in evtmap.keys():
73 btn = self._toolbar.findChild(name=k)
74 btn.setEnterCallback(self.onBtnEnter)
75 btn.setExitCallback(self.onBtnExit)
76
77 #self._toolbar.adaptLayout()
78 self._toolbar.show()
79 self._toolbar.x = 10
80 self._toolbar.y = 50
81
82 def hide(self):
83 self._toolbar.hide()
84
85 def _enableBtn(self, enabled, btn):
86 pass
87
88 def enableInsert(self, enabled):
89 self._enableBtn(enabled, self._toolbar.findChild(name='btnInsert'))
90
91 def enableDelete(self, enabled):
92 self._enableBtn(enabled, self._toolbar.findChild(name='btnDelete'))
93
94 def enableSelect(self, enabled):
95 self._enableBtn(enabled, self._toolbar.findChild(name='btnSelect'))
96
97 class StatusBar(object):
98 def __init__(self, screenw, screenh):
99 self._statusbar = pychan.loadXML('content/gui/statuspanel.xml')
100 self._statusbar.show()
101 height = 25
102 self._statusbar.position = (0, screenh - height)
103 self._statusbar.size = (screenw, height)
104 self.statustxt = ''
105 self.lbl = self._statusbar.findChild(name='lblStatus')
106
107 def setStatus(self, msg):
108 self.statustxt = msg
109 self.lbl.text = ' ' + msg
110 self.lbl.resizeToContent()
111
112 def showTooltip(self, elem):
113 self.lbl.text = elem.helptext
114 self.lbl.resizeToContent()
115
116 def hideTooltip(self, elem):
117 self.lbl.text = self.statustxt
118 self.lbl.resizeToContent()
119
120
121 class MapEditor(plugin.Plugin,fife.IMouseListener, fife.IKeyListener):
122 def __init__(self, engine):
123 self._engine = engine
124 eventmanager = self._engine.getEventManager()
125 eventmanager.setNonConsumableKeys([
126 fife.Key.LEFT,
127 fife.Key.RIGHT,
128 fife.Key.UP,
129 fife.Key.DOWN])
130 fife.IMouseListener.__init__(self)
131 eventmanager.addMouseListener(self)
132 fife.IKeyListener.__init__(self)
133 eventmanager.addKeyListener(self)
134
135 # Fifedit plugin data
136 self.menu_items = { 'Select Map' : self._selectMap }
137
138 self._camera = None # currently selected camera
139 self._map = None # currently selected map
140 self._layer = None # currently selected layer
141 self._object = None # currently selected object
142 self._selection = None # currently selected coordinates
143 self._instances = None # currently selected instances
144
145 self._ctrldown = False
146 self._shiftdown = False
147 self._altdown = False
148 self._dragx = NOT_INITIALIZED
149 self._dragy = NOT_INITIALIZED
150
151 self._mapselector = MapSelection(self._selectLayer, self._selectObject)
152 self._objectselector = None
153 rb = self._engine.getRenderBackend()
154 self._statusbar = StatusBar(rb.getWidth(), rb.getHeight())
155 self._toolbar = Toolbar(cbwa(self._setMode, VIEWING), cbwa(self._setMode, MOVING),
156 cbwa(self._setMode, INSERTING), cbwa(self._setMode, REMOVING),
157 self._statusbar.showTooltip, self._statusbar.hideTooltip)
158 self._toolbar.show()
159 self._setMode(NOTHING_LOADED)
160
161 def _assert(self, statement, msg):
162 if not statement:
163 print msg
164 raise EditorLogicError(msg)
165
166 def _setMode(self, mode):
167 if (mode != NOTHING_LOADED) and (not self._camera):
168 self._statusbar.setStatus('Please load map first')
169 return
170 if (mode == INSERTING) and (not self._object):
171 self._statusbar.setStatus('Please select object first')
172 return
173 self._mode = mode
174 print "Entered mode " + mode
175 self._statusbar.setStatus(mode.replace('_', ' ').capitalize())
176
177 # gui for selecting a map
178 def _selectMap(self):
179 Selection([map.getId() for map in self._engine.getModel().getMaps()], self.editMap)
180
181 def _selectDefaultCamera(self, map):
182 self._camera = None
183
184 self._engine.getView().resetRenderers()
185 for cam in self._engine.getView().getCameras():
186 cam.setEnabled(False)
187
188 for cam in self._engine.getView().getCameras():
189 if cam.getLocationRef().getMap().getId() == map.getId():
190 rb = self._engine.getRenderBackend()
191 cam.setViewPort(fife.Rect(0, 0, rb.getScreenWidth(), rb.getScreenHeight()))
192 cam.setEnabled(True)
193 self._camera = cam
194 break
195 if not self._camera:
196 raise AttributeError('No cameras found associated with this map: ' + map.getId())
197
198 def editMap(self, mapid):
199 self._camera = None
200 self._map = None
201 self._layer = None
202 self._object = None
203 self._selection = None
204 self._instances = None
205 self._setMode(NOTHING_LOADED)
206
207 self._map = self._engine.getModel().getMap(mapid)
208 if not self._map.getLayers():
209 raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
210
211 self._layer = self._map.getLayers()[0]
212 self._selectDefaultCamera(self._map)
213 self._setMode(VIEWING)
214
215 self._mapselector.show(self._map)
216
217 def _selectLayer(self):
218 Selection([layer.getId() for layer in self._map.getLayers()], self._editLayer)
219
220 def _editLayer(self, layerid):
221 self._layer = None
222 layers = [l for l in self._map.getLayers() if l.getId() == layerid]
223 self._assert(len(layers) == 1, 'Layer amount != 1')
224 self._layer = layers[0]
225
226 def _selectObject(self):
227 if not self._objectselector:
228 self._objectselector = ObjectSelector(self._engine, self._map, self._editObject)
229 self._objectselector.show()
230
231 def _editObject(self, object):
232 self._object = object
233
234 def _selectCell(self, screenx, screeny, preciseCoords=False):
235 self._assert(self._camera, 'No camera bind yet, cannot select any cell')
236
237 self._selection = self._camera.toMapCoordinates(fife.ScreenPoint(screenx, screeny), False)
238 self._selection.z = 0
239 loc = fife.Location(self._layer)
240 if preciseCoords:
241 self._selection = self._layer.getCellGrid().toExactLayerCoordinates(self._selection)
242 loc.setExactLayerCoordinates(self._selection)
243 else:
244 self._selection = self._layer.getCellGrid().toLayerCoordinates(self._selection)
245 loc.setLayerCoordinates(self._selection)
246 fife.CellSelectionRenderer.getInstance(self._camera).selectLocation(loc)
247 return loc
248
249 def _getInstancesFromSelection(self, top_only):
250 self._assert(self._layer, 'No layer assigned in _getInstancesFromSelection')
251 self._assert(self._selection, 'No selection assigned in _getInstancesFromSelection')
252 self._assert(self._camera, 'No camera assigned in _getInstancesFromSelection')
253
254 loc = fife.Location(self._layer)
255 if type(self._selection) == fife.ExactModelCoordinate:
256 loc.setExactLayerCoordinates(self._selection)
257 else:
258 loc.setLayerCoordinates(self._selection)
259 instances = self._camera.getMatchingInstances(loc)
260 if top_only and (len(instances) > 0):
261 instances = [instances[0]]
262 return instances
263
264 def _placeInstance(self):
265 mname = '_placeInstance'
266 self._assert(self._object, 'No object assigned in %s' % mname)
267 self._assert(self._selection, 'No selection assigned in %s' % mname)
268 self._assert(self._layer, 'No layer assigned in %s' % mname)
269 self._assert(self._mode == INSERTING, 'No mode is not INSERTING in %s (is instead %s)' % (mname, str(self._mode)))
270
271 # don't place repeat instances
272 for i in self._getInstancesFromSelection(False):
273 if i.getObject().getId() == self._object.getId():
274 print 'Warning: attempt to place duplicate instance of object %s. Ignoring request.' % self._object.getId()
275 return
276
277 inst = self._layer.createInstance(self._object, self._selection)
278 fife.InstanceVisual.create(inst)
279
280 def _removeInstances(self):
281 mname = '_removeInstances'
282 self._assert(self._selection, 'No selection assigned in %s' % mname)
283 self._assert(self._layer, 'No layer assigned in %s' % mname)
284 self._assert(self._mode == REMOVING, 'Mode is not REMOVING in %s (is instead %s)' % (mname, str(self._mode)))
285
286 for i in self._getInstancesFromSelection(top_only=True):
287 print "deleting " + str(i)
288 self._layer.deleteInstance(i)
289
290 def _moveInstances(self):
291 mname = '_removeInstances'
292 self._assert(self._selection, 'No selection assigned in %s' % mname)
293 self._assert(self._layer, 'No layer assigned in %s' % mname)
294 self._assert(self._mode == MOVING, 'Mode is not MOVING in %s (is instead %s)' % (mname, str(self._mode)))
295
296 loc = fife.Location(self._layer)
297 if self._shiftdown:
298 loc.setExactLayerCoordinates(self._selection)
299 else:
300 loc.setLayerCoordinates(self._selection)
301 for i in self._instances:
302 i.setLocation(loc)
303
304 def _rotateInstances(self):
305 mname = '_rotateInstances'
306 self._assert(self._selection, 'No selection assigned in %s' % mname)
307 self._assert(self._layer, 'No layer assigned in %s' % mname)
308
309 for i in self._getInstancesFromSelection(top_only=True):
310 i.setRotation((i.getRotation() + 90) % 360)
311 ## Surprisingly, the following "snap-to-rotation" code is actually incorrect. Object
312 ## rotation is independent of the camera, whereas the choice of an actual rotation image
313 ## depends very much on how the camera is situated. For example, suppose an object has
314 ## rotations defined for 45,135,225,315. And suppose the camera position results in an
315 ## effective 60 degree rotation. If the object is given a rotation of 0, then the (correct)
316 ## final rotation value of 45 (which is closest to 60 = 0 + 60) will be chosen. If we try
317 ## to snap to the closest value to 0 (45), then an incorrect final rotation value will be
318 ## chosen: 135, which is closest to 105 = 45 + 60. --jwt
319 # ovis = i.getObject().get2dGfxVisual()
320 # curUsedAngle = ovis.getClosestMatchingAngle(i.getRotation())
321 # angles = ovis.getStaticImageAngles()
322 # if angles:
323 # ind = list(angles).index(curUsedAngle)
324 # if ind == (len(angles) - 1):
325 # ind = 0
326 # else:
327 # ind += 1
328 # i.setRotation(angles[ind])
329 # else:
330 # print "rotation not supported for this instance"
331
332 def changeRotation(self):
333 currot = self._camera.getRotation()
334 self._camera.setRotation((currot + 90) % 360)
335
336 def _moveCamera(self, screen_x, screen_y):
337 coords = self._camera.getLocationRef().getMapCoordinates()
338 z = self._camera.getZoom()
339 r = self._camera.getRotation()
340 if screen_x:
341 coords.x -= screen_x / z * math.cos(r / 180.0 * math.pi) / 100;
342 coords.y -= screen_x / z * math.sin(r / 180.0 * math.pi) / 100;
343 if screen_y:
344 coords.x -= screen_y / z * math.sin(-r / 180.0 * math.pi) / 100;
345 coords.y -= screen_y / z * math.cos(-r / 180.0 * math.pi) / 100;
346 coords = self._camera.getLocationRef().setMapCoordinates(coords)
347 self._camera.refresh()
348
349 def mousePressed(self, evt):
350 if self._ctrldown:
351 if evt.getButton() == fife.MouseEvent.LEFT:
352 self._dragx = evt.getX()
353 self._dragy = evt.getY()
354 else:
355 if self._camera:
356 self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
357 if self._mode == VIEWING:
358 self._instances = self._getInstancesFromSelection(top_only=True)
359 elif self._mode == INSERTING:
360 self._placeInstance()
361 elif self._mode == REMOVING:
362 self._removeInstances()
363 elif self._mode == MOVING:
364 self._instances = self._getInstancesFromSelection(top_only=True)
365 else:
366 self._setMode(self._mode) # refresh status
367
368 def mouseDragged(self, evt):
369 if self._ctrldown:
370 if (self._dragx != NOT_INITIALIZED) and (self._dragy != NOT_INITIALIZED):
371 self._moveCamera(evt.getX() - self._dragx, evt.getY() - self._dragy)
372 self._dragx = evt.getX()
373 self._dragy = evt.getY()
374 else:
375 if self._mode == INSERTING:
376 self._selectCell(evt.getX(), evt.getY())
377 self._placeInstance()
378 elif self._mode == REMOVING:
379 self._selectCell(evt.getX(), evt.getY())
380 self._removeInstances()
381 elif self._mode == MOVING and self._instances:
382 self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
383 self._moveInstances()
384
385 def mouseReleased(self, evt):
386 self._dragx = NOT_INITIALIZED
387 self._dragy = NOT_INITIALIZED
388
389 def mouseMoved(self, evt):
390 pass
391 def mouseEntered(self, evt):
392 pass
393 def mouseExited(self, evt):
394 pass
395 def mouseClicked(self, evt):
396 pass
397
398 def mouseWheelMovedUp(self, evt):
399 if self._ctrldown and self._camera:
400 self._camera.setZoom(self._camera.getZoom() * 1.05)
401
402 def mouseWheelMovedDown(self, evt):
403 if self._ctrldown and self._camera:
404 self._camera.setZoom(self._camera.getZoom() / 1.05)
405
406
407 def keyPressed(self, evt):
408 keyval = evt.getKey().getValue()
409 keystr = evt.getKey().getAsString().lower()
410
411 if keyval == fife.Key.LEFT:
412 self._moveCamera(50, 0)
413 elif keyval == fife.Key.RIGHT:
414 self._moveCamera(-50, 0)
415 elif keyval == fife.Key.UP:
416 self._moveCamera(0, 50)
417 elif keyval == fife.Key.DOWN:
418 self._moveCamera(0, -50)
419 elif keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
420 self._ctrldown = True
421 elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
422 self._shiftdown = True
423 elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
424 self._altdown = True
425
426 elif keyval == fife.Key.INSERT:
427 if self._mode != INSERTING:
428 self._setMode(INSERTING)
429 else:
430 self._setMode(VIEWING)
431
432 elif keyval == fife.Key.DELETE_KEY:
433 if self._mode != REMOVING:
434 self._setMode(REMOVING)
435 else:
436 self._setMode(VIEWING)
437
438 elif keystr == 'm':
439 if self._mode != MOVING:
440 self._setMode(MOVING)
441 else:
442 self._setMode(VIEWING)
443
444 elif keystr == 't':
445 gridrenderer = self._camera.getRenderer('GridRenderer')
446 gridrenderer.setEnabled(not gridrenderer.isEnabled())
447
448 elif keystr == 'b':
449 blockrenderer = self._camera.getRenderer('BlockingInfoRenderer')
450 blockrenderer.setEnabled(not blockrenderer.isEnabled())
451
452 elif keystr == 'r':
453 if self._selection:
454 self._rotateInstances()
455
456 elif keystr == 'o':
457 self.changeRotation()
458
459 def keyReleased(self, evt):
460 keyval = evt.getKey().getValue()
461 if keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
462 self._ctrldown = False
463 elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
464 self._shiftdown = False
465 elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
466 self._altdown = False
467
468