view clients/editor/scripts/gui/mapeditor.py @ 260:a0068e00f076

* Really fixed proper support for non-ascii filenames for FileBrowser * Adjusted selection cursor hotspot
author cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 08 Jun 2009 21:04:43 +0000
parents f2195628947b
children cad19fe699ee
line wrap: on
line source

# MapEditor is a plugin for Fifedit. It allows for selection and visual editing of maps.

import math

import fife
import pychan
import pychan.widgets as widgets
import scripts
import scripts.events as events
import action
from toolbar import ToolBar
from menubar import Menu, MenuBar
from action import Action, ActionGroup
from scripts.mapcontroller import MapController

states = (u'SELECTING', u'INSERTING', u'REMOVING', u'MOVING', u'OBJECTPICKER')
for s in states:
	globals()[s] = s
NOT_INITIALIZED = -9999999

class MapEditor:
	def __init__(self):	
		self._ignoreToggles = False # A hack to avoid infinite recursion when toggling a button
		self._controller = None
		self._mode = SELECTING
		
		self._editor = scripts.editor.getEditor()
		self._eventlistener = self._editor.getEventListener()
		self._statusbar = self._editor.getStatusBar()
		self._toolbar = self._editor.getToolBar()
		self._object = None
		self._startDragPos = None
		self._lastDragPos = None
		self._lastDragPosExact = None
		
		self._toolbox = self._editor.getToolbox()
		
		self._initToolbuttons()
		
		self._toolbox.show()
		self._instances = []
		
		events.postMapShown.connect(self._mapChanged)
		events.onObjectSelected.connect(self.setObject)
		self._undogroup = False
		
		self._scrollX = 0
		self._scrollY = 0
		
	def _init(self):
		self._dragx = NOT_INITIALIZED
		self._dragy = NOT_INITIALIZED
		
		self._setMode(SELECTING)
		
		self._initToolbarbuttons()
		
		events.keyPressed.connect(self.keyPressed)
		events.keyReleased.connect(self.keyReleased)
		
		events.mousePressed.connect(self.mousePressed)
		events.mouseDragged.connect(self.mouseDragged)
		events.mouseReleased.connect(self.mouseReleased)
		events.mouseMoved.connect(self.mouseMoved)
		events.mouseEntered.connect(self.mouseEntered)
		events.mouseExited.connect(self.mouseExited)
		events.mouseWheelMovedUp.connect(self.mouseWheelMovedUp)
		events.mouseWheelMovedDown.connect(self.mouseWheelMovedDown)
		events.onPump.connect(self.pump)
		
	def _clear(self):
		self._clearToolbarButtons()
		
		events.keyPressed.disconnect(self.keyPressed)
		events.keyReleased.disconnect(self.keyReleased)
		
		events.mousePressed.disconnect(self.mousePressed)
		events.mouseDragged.disconnect(self.mouseDragged)
		events.mouseReleased.disconnect(self.mouseReleased)
		events.mouseMoved.disconnect(self.mouseMoved)
		events.mouseExited.disconnect(self.mouseExited)
		events.mouseEntered.disconnect(self.mouseEntered)
		events.mouseWheelMovedUp.disconnect(self.mouseWheelMovedUp)
		events.mouseWheelMovedDown.disconnect(self.mouseWheelMovedDown)
		events.onPump.disconnect(self.pump)
		
	def _mapChanged(self, sender, mapview):
		self.setController(mapview.getController())
		
	def _setCursor(self):
		engine = self._editor.getEngine()
		cursor = engine.getCursor()
		
		id = -1
		if self._mode == SELECTING:
			id = engine.getImagePool().addResourceFromFile("gui/icons/select_instance.png")
			image = engine.getImagePool().getImage(id)
			image.setXShift(-7)
			image.setYShift(-7)
		elif self._mode == INSERTING:
			id = engine.getImagePool().addResourceFromFile("gui/icons/add_instance.png")
			image = engine.getImagePool().getImage(id)
			image.setXShift(-2)
			image.setYShift(-20)
		elif self._mode == REMOVING:
			id = engine.getImagePool().addResourceFromFile("gui/icons/erase_instance.png")
			image = engine.getImagePool().getImage(id)
			image.setXShift(-2)
			image.setYShift(-19)
		elif self._mode == MOVING:
			id = engine.getImagePool().addResourceFromFile("gui/icons/move_instance.png")
			image = engine.getImagePool().getImage(id)
			image.setXShift(-11)
			image.setYShift(-11)
		elif self._mode == OBJECTPICKER:
			id = engine.getImagePool().addResourceFromFile("gui/icons/objectpicker.png")
			image = engine.getImagePool().getImage(id)
			image.setXShift(-0)
			image.setYShift(-22)
			
		if id < 0:
			self._resetCursor()
		else:
			cursor.set(fife.CURSOR_IMAGE, id)
			
	def _resetCursor(self):
		cursor = self._editor.getEngine().getCursor()
		cursor.set(fife.CURSOR_NATIVE, fife.NC_ARROW)
		
	def setObject(self, object):
		self._object = object
		
	def setController(self, controller):
		if self._controller is not None:
			self._clear()

		if controller is not None:
			self._init()
			
		self._controller = controller
		
	def _initToolbuttons(self):
		self._selectAction = Action(text=u"Select", icon="gui/icons/select_instance.png", checkable=True)
		self._drawAction = Action(text=u"Draw", icon="gui/icons/add_instance.png", checkable=True)
		self._removeAction = Action(text=u"Remove", icon="gui/icons/erase_instance.png", checkable=True)
		self._moveAction = Action(text=u"Move", icon="gui/icons/move_instance.png", checkable=True)
		self._objectpickerAction = Action(text=u"Pick object", icon="gui/icons/objectpicker.png", checkable=True)
		
		self._selectAction.helptext = u"Select cells on layer"
		self._moveAction.helptext = u"Moves instances"
		self._drawAction.helptext = u"Adds new instances based on currently selected object"
		self._removeAction.helptext = u"Deletes instances"
		self._objectpickerAction.helptext = u"Click an instance to set the current object to the one used by instance"
		
		action.toggled.connect(self._buttonToggled, sender=self._selectAction)
		action.toggled.connect(self._buttonToggled, sender=self._moveAction)
		action.toggled.connect(self._buttonToggled, sender=self._drawAction)
		action.toggled.connect(self._buttonToggled, sender=self._removeAction)
		action.toggled.connect(self._buttonToggled, sender=self._objectpickerAction)
		
		self._toolgroup = ActionGroup(exclusive=True, name=u"Tool group")
		self._toolgroup.addAction(self._selectAction)
		self._toolgroup.addAction(self._moveAction)
		self._toolgroup.addAction(self._drawAction)
		self._toolgroup.addAction(self._removeAction)
		self._toolgroup.addAction(self._objectpickerAction)
		
		
		self._toolbox.addAction(self._toolgroup)
		self._toolbox.adaptLayout()
		
		self._editor._editMenu.addAction(self._toolgroup)
		
	def _initToolbarbuttons(self):
		rotateLeftAction = Action(text=u"Rotate counterclockwise", icon="gui/icons/rotate_countercw.png")
		rotateRightAction = Action(text=u"Rotate clockwise", icon="gui/icons/rotate_clockwise.png")
		zoomInAction = Action(text=u"Zoom in", icon="gui/icons/zoom_in.png")
		zoomOutAction = Action(text=u"Zoom out", icon="gui/icons/zoom_out.png")
		zoomResetAction = Action(text=u"Reset zoom", icon="gui/icons/zoom_default.png")
		
		action.activated.connect(self.rotateCounterClockwise, sender=rotateLeftAction)
		action.activated.connect(self.rotateClockwise, sender=rotateRightAction)
		action.activated.connect(self.zoomIn, sender=zoomInAction)
		action.activated.connect(self.zoomOut, sender=zoomOutAction)
		action.activated.connect(self.resetZoom, sender=zoomResetAction)
	
		self._viewGroup = ActionGroup(name=u"View group")
		self._viewGroup.addAction(rotateLeftAction)
		self._viewGroup.addAction(rotateRightAction)
		self._viewGroup.addAction(zoomInAction)
		self._viewGroup.addAction(zoomOutAction)
		self._viewGroup.addAction(zoomResetAction)
		
		self._toolbar.addAction(self._viewGroup)
		self._toolbar.adaptLayout()
		
	def _clearToolbarButtons(self):
		self._toolbar.removeAction(self._viewGroup)
		self._toolbar.adaptLayout()
		self._viewGroup = None
		
		
	def _setMode(self, mode):
		if (mode == INSERTING) and (not self._object):
			self._statusbar.setText(u'Please select object first')
			mode = self._mode

		self._ignoreToggles = True
		# Update toolbox buttons
		if (mode == INSERTING):
			self._drawAction.setChecked(True)
		elif mode == REMOVING:
			self._removeAction.setChecked(True)
		elif mode == MOVING:
			self._moveAction.setChecked(True)
		elif mode == OBJECTPICKER:
			self._objectpickerAction.setChecked(True)
		else:
			self._selectAction.setChecked(True)
		self._ignoreToggles = False

		self._mode = mode
		print "Entered mode " + mode
		self._statusbar.setText(mode.replace('_', ' ').capitalize())
		self._setCursor()
		
	def _buttonToggled(self, sender, toggled):
		if self._controller is None: return
		if self._ignoreToggles is True: return
	
		mode = SELECTING
		
		if toggled:
			if sender == self._selectAction:
				mode = SELECTING
			elif sender == self._moveAction:
				mode = MOVING
			elif sender == self._drawAction:
				mode = INSERTING
			elif sender == self._removeAction:
				mode = REMOVING
			elif sender == self._objectpickerAction:
				mode = OBJECTPICKER

		self._setMode(mode)
		
	def mousePressed(self, sender, event):
		if event.isConsumedByWidgets():
			return

		realCoords = self._getRealCoords(sender, event)

		if event.getButton() == fife.MouseEvent.MIDDLE:
			self._dragx = realCoords[0]
			self._dragy = realCoords[1]
			
		else:
			if event.getButton() == fife.MouseEvent.RIGHT:
				self._controller.deselectSelection()
				
			if self._mode == SELECTING:
				if event.getButton() == fife.MouseEvent.LEFT:
					if self._eventlistener.shiftPressed:
						self._controller.deselectCell(realCoords[0], realCoords[1])
					else:
						if self._eventlistener.controlPressed is False:
							self._controller.deselectSelection()
						self._controller.selectCell(realCoords[0], realCoords[1])
					
				elif event.getButton() == fife.MouseEvent.RIGHT:
					self._controller.deselectSelection()
					
			elif self._mode == INSERTING:
				if event.getButton() == fife.MouseEvent.LEFT:
					self._controller.deselectSelection()
					self._controller.selectCell(realCoords[0], realCoords[1])
					self._controller.getUndoManager().startGroup("Inserted instances")
					self._undogroup = True
					
					position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
					position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
					
					self._controller.selectCell(realCoords[0], realCoords[1])
					self._controller.placeInstance(position, self._object)
				
			elif self._mode == REMOVING:
				if event.getButton() == fife.MouseEvent.LEFT:
					self._controller.deselectSelection()
					self._controller.selectCell(realCoords[0], realCoords[1])
					self._controller.getUndoManager().startGroup("Removed instances")
					self._undogroup = True
					
					self._controller.removeInstances(self._controller.getInstancesFromSelection())
				
			elif self._mode == MOVING:
				if event.getButton() == fife.MouseEvent.LEFT:
				
					position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)

					self._lastDragPos = self._controller._layer.getCellGrid().toLayerCoordinates(position)
					self._lastDragPosExact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
	
					for loc in self._controller._selection:
						if loc.getLayerCoordinates() == self._lastDragPos:
							break
					else:
						self._controller.deselectSelection()
						self._controller.selectCell(realCoords[0], realCoords[1])
						
					self._instances = self._controller.getInstancesFromSelection()
					
					self._controller.getUndoManager().startGroup("Moved instances")
					self._undogroup = True
					
			elif self._mode == OBJECTPICKER:
				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
				exact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
				instances = self._controller.getInstancesFromPosition(exact)
				if len(instances) >= 1:
					object = instances[0].getObject()
					if object.getId() != self._object.getId() or object.getNamespace() != self._object.getNamespace():
						events.onObjectSelected.send(sender=self, object=object)

	def mouseDragged(self, sender, event):
		if event.isConsumedByWidgets():
			return
			
		realCoords = self._getRealCoords(sender, event)
			
		if event.getButton() == fife.MouseEvent.MIDDLE:
			self._scrollX = (self._dragx-realCoords[0])/10.0
			self._scrollY = (self._dragy-realCoords[1])/10.0
		else:
			if self._mode != SELECTING:
				self._controller.deselectSelection()
				
			if self._mode == SELECTING:
				if event.getButton() == fife.MouseEvent.LEFT:
					if self._eventlistener.shiftPressed:
						self._controller.deselectCell(realCoords[0], realCoords[1])
					else:
						self._controller.selectCell(realCoords[0], realCoords[1])
					
			elif self._mode == INSERTING:
				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
				position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
				
				self._controller.selectCell(realCoords[0], realCoords[1])
				self._controller.placeInstance(position, self._object)
				
			elif self._mode == REMOVING:
				self._controller.selectCell(realCoords[0], realCoords[1])
				self._controller.removeInstances(self._controller.getInstancesFromSelection())
				
			elif self._mode == MOVING:
				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
				
				positionExact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
				position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
				
				if self._eventlistener.shiftPressed:
					self._controller.moveInstances(self._instances, positionExact-self._lastDragPosExact, True)
				else:
					self._controller.moveInstances(self._instances, position-self._lastDragPos, False)
				self._lastDragPos = position
				self._lastDragPosExact = positionExact
				
				# Update selection
				self._controller.deselectSelection()
				
				for i in self._instances:
					pos = i.getLocation().getMapCoordinates()
					pos = self._controller._camera.toScreenCoordinates(pos)
					self._controller.selectCell(pos.x, pos.y)
			elif self._mode == OBJECTPICKER:
				pass

	def mouseReleased(self, sender, event):
		if event.isConsumedByWidgets():
			return
			
		if self._mode == SELECTING or self._mode == MOVING:
			instances = self._controller.getInstancesFromSelection()
			if len(instances) > 0:
				events.onInstancesSelected.send(sender=self, instances=instances)
			
		if event.getButton() == fife.MouseEvent.MIDDLE:
			self._scrollX = 0
			self._scrollY = 0
			
		realCoords = self._getRealCoords(sender, event)

		if self._undogroup:
			self._controller.getUndoManager().endGroup()
			self._undogroup = False

		self._dragx = NOT_INITIALIZED
		self._dragy = NOT_INITIALIZED

	def mouseMoved(self, sender, event):
		pass
		
	def mouseEntered(self, sender, event):
		self._setCursor()
		
	def mouseExited(self, sender, event):
		self._resetCursor()
				
	def mouseWheelMovedUp(self, event):
		if self._eventlistener.controlPressed and self._controller._camera:
			self._controller._camera.setZoom(self._controller._camera.getZoom() * 1.10)

	def mouseWheelMovedDown(self, event):
		if self._eventlistener.controlPressed and self._controller._camera:
			self._controller._camera.setZoom(self._controller._camera.getZoom() / 1.10)


	def keyPressed(self, event):
		keyval = event.getKey().getValue()
		keystr = event.getKey().getAsString().lower()
		
		if keyval == fife.Key.LEFT:
			self._controller.moveCamera(50, 0)
		elif keyval == fife.Key.RIGHT:
			self._controller.moveCamera(-50, 0)
		elif keyval == fife.Key.UP:
			self._controller.moveCamera(0, 50)
		elif keyval == fife.Key.DOWN:
			self._controller.moveCamera(0, -50)
		
		elif keyval == fife.Key.INSERT:
			self._controller.fillSelection(self._object)

		elif keyval == fife.Key.DELETE:
			self._controller.clearSelection()
			
		elif keystr == "s":
			self._setMode(SELECTING)
			
		elif keystr == "i":
			if self._mode != INSERTING:
				self._setMode(INSERTING)
			else:
				self._setMode(SELECTING)
			
		elif keystr == "r":
			if self._mode != REMOVING:
				self._setMode(REMOVING)
			else:
				self._setMode(SELECTING)

		elif keystr == 'm':
			if self._mode != MOVING:
				self._setMode(MOVING)
			else:
				self._setMode(SELECTING)

		elif keystr == 't':
			gridrenderer = self._controller._camera.getRenderer('GridRenderer')
			gridrenderer.setEnabled(not gridrenderer.isEnabled())

		elif keystr == 'b':
			blockrenderer = self._controller._camera.getRenderer('BlockingInfoRenderer')
			blockrenderer.setEnabled(not blockrenderer.isEnabled())

		elif keystr == 'z':
			if self._eventlistener.controlPressed:
				if self._eventlistener.altPressed:
					if self._eventlistener.shiftPressed:
						self._controller.getUndoManager().previousBranch()
					else:
						self._controller.getUndoManager().nextBranch()
				else:
					if self._eventlistener.shiftPressed:
						self._controller.redo()
					else:
						self._controller.undo()
			

	def keyReleased(self, event):		
		pass
		
	def zoomIn(self, zoom=1.10):
		self._controller.setZoom(self._controller.getZoom()*zoom)
		
	def zoomOut(self, zoom=1.10):
		self._controller.setZoom(self._controller.getZoom()/zoom)
		
	def resetZoom(self):
		self._controller.setZoom(1)
		
	def rotateCounterClockwise(self):
		self._controller.rotateCounterClockwise()
		
	def rotateClockwise(self):
		self._controller.rotateClockwise()
			
	def _getRealCoords(self, sender, event):
		cw = sender
		offsetX = event.getX()
		offsetY = event.getY()
		
		parent = cw
		while parent is not None:
			if isinstance(parent, widgets.Widget):
				offsetX += parent.x
				offsetY += parent.y
				parent = parent.parent
			else:
				break
			
		return (offsetX, offsetY)
		
	def pump(self):
		self._controller.moveCamera(self._scrollX, self._scrollY)