diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/mapeditor.py	Sun Jun 29 18:44:17 2008 +0000
@@ -0,0 +1,468 @@
+# MapEditor is a plugin for Fifedit. It allows for selection and visual editing of maps.
+# MapEditor must be pumped (see pump).
+
+import math
+
+import fife
+import plugin
+import pychan
+import pychan.widgets as widgets
+from pychan.tools import callbackWithArguments as cbwa
+from selection import Selection, ClickSelection
+from plugins.objectselector import ObjectSelector
+
+from pychan.manager import DEFAULT_STYLE
+DEFAULT_STYLE['default']['base_color'] = fife.Color(85,128,151)
+
+states = ('NOTHING_LOADED', 'VIEWING', 'INSERTING', 'REMOVING', 'MOVING')
+for s in states:
+	globals()[s] = s
+NOT_INITIALIZED = -9999999
+
+class EditorLogicError(Exception):
+	pass
+
+class MapSelection(object):
+	def __init__(self, onLayerSelect, onObjectSelect):
+		self._mapedit = None
+		self._onLayerSelect = onLayerSelect
+		self._onObjectSelect = onObjectSelect
+
+	def show(self, map):
+		if not self._mapedit:
+			self._mapedit = pychan.loadXML('content/gui/mapeditor.xml')
+			self._mapedit.mapEvents({
+				'layerButton'  : self._onLayerSelect,
+				'objButton'   : self._onObjectSelect,
+				'closeButton' : self.hide
+			})
+		fields = self._mapedit.findChild(name='Properties')
+		# Clear previously added children
+		fields.removeChildren(*fields.children)
+		hbox = widgets.HBox()
+		fields.addChild(hbox)
+		label = widgets.Label(text='ID',min_size=(80,0))
+		hbox.addChild(label)
+		field = widgets.TextField(text=map.getId(),min_size=(100,0))
+		hbox.addChild(field)
+		self._mapedit.adaptLayout()
+		self._mapedit.show()
+		self._mapedit.x = 10
+		self._mapedit.y = 580
+
+	def hide(self):
+		self._mapedit.hide()
+
+class Toolbar(object):
+	def __init__(self, onSelect, onMove, onInsert, onDelete, onBtnEnter, onBtnExit):
+		self._onSelect, self._onMove, self._onInsert, self._onDelete = onSelect, onMove, onInsert, onDelete
+		self.onBtnEnter, self.onBtnExit = onBtnEnter, onBtnExit
+		self._toolbar = None
+	
+	def show(self):
+		if not self._toolbar:
+			self._toolbar = pychan.loadXML('content/gui/tools.xml')
+			evtmap = {
+				'btnSelect' : self._onSelect,
+				'btnMove' : self._onMove,
+				'btnInsert' : self._onInsert,
+				'btnDelete' : self._onDelete
+			}
+			self._toolbar.mapEvents(evtmap)
+			for k in evtmap.keys():
+				btn = self._toolbar.findChild(name=k)
+				btn.setEnterCallback(self.onBtnEnter)
+				btn.setExitCallback(self.onBtnExit)
+		
+		#self._toolbar.adaptLayout()
+		self._toolbar.show()
+		self._toolbar.x = 10
+		self._toolbar.y = 50
+	
+	def hide(self):
+		self._toolbar.hide()
+	
+	def _enableBtn(self, enabled, btn):
+		pass
+	
+	def enableInsert(self, enabled):
+		self._enableBtn(enabled, self._toolbar.findChild(name='btnInsert'))
+		
+	def enableDelete(self, enabled):
+		self._enableBtn(enabled, self._toolbar.findChild(name='btnDelete'))
+
+	def enableSelect(self, enabled):
+		self._enableBtn(enabled, self._toolbar.findChild(name='btnSelect'))
+	
+class StatusBar(object):
+	def __init__(self, screenw, screenh):
+		self._statusbar = pychan.loadXML('content/gui/statuspanel.xml')
+		self._statusbar.show()
+		height = 25
+		self._statusbar.position = (0, screenh - height)
+		self._statusbar.size = (screenw, height)
+		self.statustxt = ''
+		self.lbl = self._statusbar.findChild(name='lblStatus')
+
+	def setStatus(self, msg):
+		self.statustxt = msg
+		self.lbl.text = '  ' + msg
+		self.lbl.resizeToContent()
+	
+	def showTooltip(self, elem):
+		self.lbl.text = elem.helptext
+		self.lbl.resizeToContent()
+
+	def hideTooltip(self, elem):
+		self.lbl.text = self.statustxt
+		self.lbl.resizeToContent()
+
+
+class MapEditor(plugin.Plugin,fife.IMouseListener, fife.IKeyListener):
+	def __init__(self, engine):
+		self._engine = engine
+		eventmanager = self._engine.getEventManager()
+		eventmanager.setNonConsumableKeys([
+			fife.Key.LEFT,
+			fife.Key.RIGHT,
+			fife.Key.UP,
+			fife.Key.DOWN])
+		fife.IMouseListener.__init__(self)
+		eventmanager.addMouseListener(self)
+		fife.IKeyListener.__init__(self)
+		eventmanager.addKeyListener(self)
+
+		# Fifedit plugin data
+		self.menu_items = { 'Select Map' : self._selectMap }
+
+		self._camera = None     # currently selected camera
+		self._map = None        # currently selected map
+		self._layer = None      # currently selected layer
+		self._object = None     # currently selected object
+		self._selection = None  # currently selected coordinates
+		self._instances = None  # currently selected instances
+
+		self._ctrldown = False
+		self._shiftdown = False
+		self._altdown = False
+		self._dragx = NOT_INITIALIZED
+		self._dragy = NOT_INITIALIZED
+		
+		self._mapselector = MapSelection(self._selectLayer, self._selectObject)
+		self._objectselector = None
+		rb = self._engine.getRenderBackend()
+		self._statusbar = StatusBar(rb.getWidth(), rb.getHeight())
+		self._toolbar = Toolbar(cbwa(self._setMode, VIEWING), cbwa(self._setMode, MOVING),
+		                        cbwa(self._setMode, INSERTING), cbwa(self._setMode, REMOVING),
+					self._statusbar.showTooltip, self._statusbar.hideTooltip)
+		self._toolbar.show()
+		self._setMode(NOTHING_LOADED)
+
+	def _assert(self, statement, msg):
+		if not statement:
+			print msg
+			raise EditorLogicError(msg)
+	
+	def _setMode(self, mode):
+		if (mode != NOTHING_LOADED) and (not self._camera):
+			self._statusbar.setStatus('Please load map first')
+			return
+		if (mode == INSERTING) and (not self._object):
+			self._statusbar.setStatus('Please select object first')
+			return
+		self._mode = mode
+		print "Entered mode " + mode
+		self._statusbar.setStatus(mode.replace('_', ' ').capitalize())
+	
+	# gui for selecting a map
+	def _selectMap(self):
+		Selection([map.getId() for map in self._engine.getModel().getMaps()], self.editMap)
+
+	def _selectDefaultCamera(self, map):
+		self._camera = None
+		
+		self._engine.getView().resetRenderers()
+		for cam in self._engine.getView().getCameras():
+			cam.setEnabled(False)
+			
+		for cam in self._engine.getView().getCameras():
+			if cam.getLocationRef().getMap().getId() == map.getId():
+				rb = self._engine.getRenderBackend()
+				cam.setViewPort(fife.Rect(0, 0, rb.getScreenWidth(), rb.getScreenHeight()))
+				cam.setEnabled(True)
+				self._camera = cam
+				break
+		if not self._camera:
+			raise AttributeError('No cameras found associated with this map: ' + map.getId())
+
+	def editMap(self, mapid):
+		self._camera = None
+		self._map = None
+		self._layer = None
+		self._object = None
+		self._selection = None
+		self._instances = None
+		self._setMode(NOTHING_LOADED)
+		
+		self._map = self._engine.getModel().getMap(mapid)
+		if not self._map.getLayers():
+			raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
+		
+		self._layer = self._map.getLayers()[0]
+		self._selectDefaultCamera(self._map)
+		self._setMode(VIEWING)
+		
+		self._mapselector.show(self._map)
+	
+	def _selectLayer(self):
+		Selection([layer.getId() for layer in self._map.getLayers()], self._editLayer)
+
+	def _editLayer(self, layerid):
+		self._layer = None
+		layers = [l for l in self._map.getLayers() if l.getId() == layerid]
+		self._assert(len(layers) == 1, 'Layer amount != 1')
+		self._layer = layers[0]
+
+	def _selectObject(self):
+		if not self._objectselector:
+			self._objectselector = ObjectSelector(self._engine, self._map, self._editObject)
+		self._objectselector.show()
+
+	def _editObject(self, object):
+		self._object = object
+
+	def _selectCell(self, screenx, screeny, preciseCoords=False):
+		self._assert(self._camera, 'No camera bind yet, cannot select any cell')
+		
+		self._selection = self._camera.toMapCoordinates(fife.ScreenPoint(screenx, screeny), False)
+		self._selection.z = 0
+		loc = fife.Location(self._layer)
+		if preciseCoords:
+			self._selection = self._layer.getCellGrid().toExactLayerCoordinates(self._selection)
+			loc.setExactLayerCoordinates(self._selection)
+		else:
+			self._selection = self._layer.getCellGrid().toLayerCoordinates(self._selection)
+			loc.setLayerCoordinates(self._selection)
+		fife.CellSelectionRenderer.getInstance(self._camera).selectLocation(loc)
+		return loc
+
+	def _getInstancesFromSelection(self, top_only):
+		self._assert(self._layer, 'No layer assigned in _getInstancesFromSelection')
+		self._assert(self._selection, 'No selection assigned in _getInstancesFromSelection')
+		self._assert(self._camera, 'No camera assigned in _getInstancesFromSelection')
+		
+		loc = fife.Location(self._layer)
+		if type(self._selection) == fife.ExactModelCoordinate:
+			loc.setExactLayerCoordinates(self._selection)
+		else:
+			loc.setLayerCoordinates(self._selection)
+		instances = self._camera.getMatchingInstances(loc)
+		if top_only and (len(instances) > 0):
+			instances = [instances[0]]
+		return instances
+	
+	def _placeInstance(self):
+		mname = '_placeInstance'
+		self._assert(self._object, 'No object assigned in %s' % mname)
+		self._assert(self._selection, 'No selection assigned in %s' % mname)
+		self._assert(self._layer, 'No layer assigned in %s' % mname)
+		self._assert(self._mode == INSERTING, 'No mode is not INSERTING in %s (is instead %s)' % (mname, str(self._mode)))
+		
+		# don't place repeat instances
+		for i in self._getInstancesFromSelection(False):
+			if i.getObject().getId() == self._object.getId():
+				print 'Warning: attempt to place duplicate instance of object %s. Ignoring request.' % self._object.getId()
+				return
+
+		inst = self._layer.createInstance(self._object, self._selection)
+		fife.InstanceVisual.create(inst)
+
+	def _removeInstances(self):
+		mname = '_removeInstances'
+		self._assert(self._selection, 'No selection assigned in %s' % mname)
+		self._assert(self._layer, 'No layer assigned in %s' % mname)
+		self._assert(self._mode == REMOVING, 'Mode is not REMOVING in %s (is instead %s)' % (mname, str(self._mode)))
+		
+		for i in self._getInstancesFromSelection(top_only=True):
+			print "deleting " + str(i)
+			self._layer.deleteInstance(i)
+				
+	def _moveInstances(self):
+		mname = '_removeInstances'
+		self._assert(self._selection, 'No selection assigned in %s' % mname)
+		self._assert(self._layer, 'No layer assigned in %s' % mname)
+		self._assert(self._mode == MOVING, 'Mode is not MOVING in %s (is instead %s)' % (mname, str(self._mode)))
+		
+		loc = fife.Location(self._layer)
+		if self._shiftdown:
+			loc.setExactLayerCoordinates(self._selection)
+		else:
+			loc.setLayerCoordinates(self._selection)
+		for i in self._instances:
+			i.setLocation(loc)
+	
+	def _rotateInstances(self):
+		mname = '_rotateInstances'
+		self._assert(self._selection, 'No selection assigned in %s' % mname)
+		self._assert(self._layer, 'No layer assigned in %s' % mname)
+		
+		for i in self._getInstancesFromSelection(top_only=True):
+			i.setRotation((i.getRotation() + 90) % 360)
+##    Surprisingly, the following "snap-to-rotation" code is actually incorrect. Object
+##    rotation is independent of the camera, whereas the choice of an actual rotation image
+##    depends very much on how the camera is situated. For example, suppose an object has
+##    rotations defined for 45,135,225,315. And suppose the camera position results in an
+##    effective 60 degree rotation. If the object is given a rotation of 0, then the (correct)
+##    final rotation value of 45 (which is closest to 60 = 0 + 60) will be chosen. If we try
+##    to snap to the closest value to 0 (45), then an incorrect final rotation value will be
+##    chosen: 135, which is closest to 105 = 45 + 60. --jwt
+#			ovis = i.getObject().get2dGfxVisual()
+#			curUsedAngle = ovis.getClosestMatchingAngle(i.getRotation())
+#			angles = ovis.getStaticImageAngles()
+#			if angles:
+#				ind = list(angles).index(curUsedAngle)
+#				if ind == (len(angles) - 1):
+#					ind = 0
+#				else:
+#					ind += 1
+#				i.setRotation(angles[ind])
+#			else:
+#				print "rotation not supported for this instance"
+
+	def changeRotation(self):
+		currot = self._camera.getRotation()
+		self._camera.setRotation((currot + 90) % 360)
+	
+	def _moveCamera(self, screen_x, screen_y):
+		coords = self._camera.getLocationRef().getMapCoordinates()
+		z = self._camera.getZoom()
+		r = self._camera.getRotation()
+		if screen_x:
+			coords.x -= screen_x / z * math.cos(r / 180.0 * math.pi) / 100;
+			coords.y -= screen_x / z * math.sin(r / 180.0 * math.pi) / 100;
+		if screen_y:
+			coords.x -= screen_y / z * math.sin(-r / 180.0 * math.pi) / 100;
+			coords.y -= screen_y / z * math.cos(-r / 180.0 * math.pi) / 100;
+		coords = self._camera.getLocationRef().setMapCoordinates(coords)
+		self._camera.refresh()
+	
+	def mousePressed(self, evt):
+		if self._ctrldown:
+			if evt.getButton() == fife.MouseEvent.LEFT:
+				self._dragx = evt.getX()
+				self._dragy = evt.getY()
+		else:
+			if self._camera:
+				self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
+			if self._mode == VIEWING:
+				self._instances = self._getInstancesFromSelection(top_only=True)
+			elif self._mode == INSERTING:
+				self._placeInstance()
+			elif self._mode == REMOVING:
+				self._removeInstances()
+			elif self._mode == MOVING:
+				self._instances = self._getInstancesFromSelection(top_only=True)
+			else:
+				self._setMode(self._mode) # refresh status
+	
+	def mouseDragged(self, evt):
+		if self._ctrldown:
+			if (self._dragx != NOT_INITIALIZED) and (self._dragy != NOT_INITIALIZED):
+				self._moveCamera(evt.getX() - self._dragx, evt.getY() - self._dragy)
+			self._dragx = evt.getX()
+			self._dragy = evt.getY()
+		else:
+			if self._mode == INSERTING:
+				self._selectCell(evt.getX(), evt.getY())
+				self._placeInstance()
+			elif self._mode == REMOVING:
+				self._selectCell(evt.getX(), evt.getY())
+				self._removeInstances()
+			elif self._mode == MOVING and self._instances:
+				self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
+				self._moveInstances()
+
+	def mouseReleased(self, evt):
+		self._dragx = NOT_INITIALIZED
+		self._dragy = NOT_INITIALIZED
+	
+	def mouseMoved(self, evt):
+		pass
+	def mouseEntered(self, evt):
+		pass
+	def mouseExited(self, evt):
+		pass
+	def mouseClicked(self, evt):
+		pass
+	
+	def mouseWheelMovedUp(self, evt):
+		if self._ctrldown and self._camera:
+			self._camera.setZoom(self._camera.getZoom() * 1.05)
+			
+	def mouseWheelMovedDown(self, evt):
+		if self._ctrldown and self._camera:
+			self._camera.setZoom(self._camera.getZoom() / 1.05)
+			
+
+	def keyPressed(self, evt):
+		keyval = evt.getKey().getValue()
+		keystr = evt.getKey().getAsString().lower()
+		
+		if keyval == fife.Key.LEFT:
+			self._moveCamera(50, 0)
+		elif keyval == fife.Key.RIGHT:
+			self._moveCamera(-50, 0)
+		elif keyval == fife.Key.UP:
+			self._moveCamera(0, 50)
+		elif keyval == fife.Key.DOWN:
+			self._moveCamera(0, -50)
+		elif keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
+			self._ctrldown = True
+		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
+			self._shiftdown = True
+		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
+			self._altdown = True
+		
+		elif keyval == fife.Key.INSERT:
+			if self._mode != INSERTING:
+				self._setMode(INSERTING)
+			else:
+				self._setMode(VIEWING)
+
+		elif keyval == fife.Key.DELETE_KEY:
+			if self._mode != REMOVING:
+				self._setMode(REMOVING)
+			else:
+				self._setMode(VIEWING)
+			
+		elif keystr == 'm':
+			if self._mode != MOVING:
+				self._setMode(MOVING)
+			else:
+				self._setMode(VIEWING)
+		
+		elif keystr == 't':
+			gridrenderer = self._camera.getRenderer('GridRenderer')
+			gridrenderer.setEnabled(not gridrenderer.isEnabled())
+
+		elif keystr == 'b':
+			blockrenderer = self._camera.getRenderer('BlockingInfoRenderer')
+			blockrenderer.setEnabled(not blockrenderer.isEnabled())
+		
+		elif keystr == 'r':
+			if self._selection:
+				self._rotateInstances()
+
+		elif keystr == 'o':
+			self.changeRotation()
+	
+	def keyReleased(self, evt):
+		keyval = evt.getKey().getValue()
+		if keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
+			self._ctrldown = False
+		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
+			self._shiftdown = False
+		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
+			self._altdown = False
+
+