diff tools/editor/plugins/ObjectSelector.py @ 378:64738befdf3b

bringing in the changes from the build_system_rework branch in preparation for the 0.3.0 release. This commit will require the Jan2010 devkit. Clients will also need to be modified to the new way to import fife.
author vtchill@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 11 Jan 2010 23:34:52 +0000
parents
children fa1373b9fa16
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tools/editor/plugins/ObjectSelector.py	Mon Jan 11 23:34:52 2010 +0000
@@ -0,0 +1,443 @@
+# coding: utf-8
+
+from fife.extensions import pychan
+from fife.extensions.pychan import widgets, tools, attrs, internal
+from fife.extensions.pychan.tools import callbackWithArguments
+import scripts
+import scripts.plugin as plugin
+from scripts.events import *
+from scripts.gui.action import Action
+from fife import fife
+from fife.fife import Color
+
+# TODO:
+# - Clean up code
+# - Better event handling
+
+_DEFAULT_BASE_COLOR = internal.DEFAULT_STYLE['default']['base_color']
+_DEFAULT_SELECTION_COLOR = internal.DEFAULT_STYLE['default']['selection_color']
+_DEFAULT_COLOR_STEP = Color(10, 10, 10)
+
+class ObjectIcon(widgets.VBox):
+	""" The ObjectIcon is used to represent the object in the object selector.
+	"""	
+	ATTRIBUTES = widgets.VBox.ATTRIBUTES + [ attrs.Attr("text"), attrs.Attr("image"), attrs.BoolAttr("selected") ]
+	
+	def __init__(self,callback,**kwargs):
+		super(ObjectIcon,self).__init__(**kwargs)
+
+		self.callback = callback	
+
+		self.capture(self._mouseEntered, "mouseEntered")
+		self.capture(self._mouseExited, "mouseExited")
+		self.capture(self._mouseClicked, "mouseClicked")
+
+		vbox = widgets.VBox(padding=3)
+
+		# Icon
+		self.icon = widgets.Icon(**kwargs)
+		self.addChild(self.icon)
+
+		# Label
+		hbox = widgets.HBox(padding=1)
+		self.addChild(hbox)
+		self.label = widgets.Label(**kwargs)
+		hbox.addChild(self.label)
+
+	def _setText(self, text):
+		self.label.text = text
+		
+	def _getText(self):
+		return self.label.text
+	text = property(_getText, _setText)
+
+	def _setImage(self, image):
+		self.icon.image = image
+
+	def _getImage(self):
+		return self.icon.image
+	image = property(_getImage, _setImage)
+
+	def _setSelected(self, enabled):
+		if isinstance(self.parent, ObjectIconList):
+			if enabled == True:
+				self.parent.selected_item = self
+			else:
+				if self.selected:
+					self.parent.selected_item = None
+		
+		if self.selected:
+			self.base_color = _DEFAULT_SELECTION_COLOR
+		else:
+			self.base_color = _DEFAULT_BASE_COLOR
+
+	def _isSelected(self):
+		if isinstance(self.parent, ObjectIconList):
+			return self == self.parent.selected_item
+		return False
+	selected = property(_isSelected, _setSelected)
+
+	#--- Event handling ---#
+	def _mouseEntered(self, event):
+		self.base_color += _DEFAULT_COLOR_STEP
+
+	def _mouseExited(self, event):
+		self.base_color -= _DEFAULT_COLOR_STEP
+
+	def _mouseClicked(self, event):
+		self.selected = True
+		self.callback()
+
+class ObjectIconList(widgets.VBox):
+	ATTRIBUTES = widgets.VBox.ATTRIBUTES
+	
+	def __init__(self,**kwargs):
+		super(ObjectIconList, self).__init__(max_size=(5000,500000), **kwargs)
+		self.base_color = self.background_color
+
+		self.capture(self._keyPressed, "keyPressed")
+		#self.capture(self._keyPressed, "keyReleased")
+		self._selectedItem = None
+		self.is_focusable = True
+
+	def _keyPressed(self, event):
+		print "KeyEvent", event
+
+	def clear(self):
+		for c in reversed(self.children):
+			self.removeChild(c)
+
+	def _setSelectedItem(self, item):
+		if isinstance(item, ObjectIcon) or item is None:
+			if self._selectedItem is not None:
+				tmp = self._selectedItem
+				self._selectedItem = item
+				tmp.selected = False
+			else:
+				self._selectedItem = item
+		#if item is not None:
+		#	item.selected = True
+
+	def _getSelectedItem(self):
+		return self._selectedItem
+	selected_item = property(_getSelectedItem, _setSelectedItem)
+	
+class ObjectSelector(plugin.Plugin):
+	"""The ObjectSelector class offers a gui Widget that let's you select the object you
+	wish to use to in the editor.
+	@param engine: fife instance
+	@param map: fife.Map instance containing your map
+	@param selectNotify: callback function used to tell the editor you selected an object.
+	"""
+	def __init__(self):
+		self.editor = None
+		self.engine = None
+		self.mode = 'list' # Other mode is 'preview'
+		
+		self._enabled = False
+		self.object = None
+
+	def enable(self):
+		if self._enabled is True:
+			return
+			
+		self.editor = scripts.editor.getEditor()
+		self.engine = self.editor.getEngine()
+			
+		self._showAction = Action(u"Object selector", checkable=True)
+		scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
+		
+		self.editor._tools_menu.addAction(self._showAction)
+		
+		events.postMapShown.connect(self.update_namespace)
+		events.onObjectSelected.connect(self.setPreview)
+		events.onObjectsImported.connect(self.update_namespace)
+		
+		self.buildGui()
+
+	def disable(self):
+		if self._enabled is False:
+			return
+			
+		self.gui.hide()
+		self.removeAllChildren()
+		
+		events.postMapShown.disconnect(self.update_namespace)
+		events.onObjectSelected.disconnect(self.setPreview)
+		events.onObjectsImported.disconnect(self.update_namespace)
+		
+		self.editor._tools_menu.removeAction(self._showAction)
+
+	def isEnabled(self):
+		return self._enabled;
+
+	def getName(self):
+		return "Object selector"
+		
+
+	def buildGui(self):
+		self.gui = pychan.loadXML('gui/objectselector.xml')
+
+		# Add search field
+		self._searchfield = self.gui.findChild(name="searchField")
+		self._searchfield.capture(self._search)
+		self._searchfield.capture(self._search, "keyPressed")
+		self.gui.findChild(name="searchButton").capture(self._search)
+		
+		# Add the drop down with list of namespaces
+		self.namespaces = self.gui.findChild(name="namespaceDropdown")
+		self.namespaces.items = self.engine.getModel().getNamespaces()
+		self.namespaces.selected = 0
+
+		# TODO: Replace with SelectionEvent, once pychan supports it
+		self.namespaces.capture(self.update_namespace, "action")
+		self.namespaces.capture(self.update_namespace, "mouseWheelMovedUp")
+		self.namespaces.capture(self.update_namespace, "mouseWheelMovedDown")
+		self.namespaces.capture(self.update_namespace, "keyReleased")
+
+		# Object list
+		self.mainScrollArea = self.gui.findChild(name="mainScrollArea")
+		self.objects = None
+		if self.mode == 'list':
+			self.setTextList()
+		else: # Assuming self.mode is 'preview'
+			self.setImageList()
+
+		# Action buttons
+		self.gui.findChild(name="toggleModeButton").capture(self.toggleMode)
+		self.gui.findChild(name="closeButton").capture(self.hide)
+
+		# Preview area
+		self.gui.findChild(name="previewScrollArea").background_color = self.gui.base_color
+		self.preview = self.gui.findChild(name="previewIcon")
+		
+
+	def toggleMode(self):
+		if self.mode == 'list':
+			self.setImageList()
+			self.mode = 'preview'
+		elif self.mode == 'preview':
+			self.setTextList()
+			self.mode = 'list'
+		self.update()
+
+
+	def setImageList(self):
+		"""Sets the mainScrollArea to contain a Vbox that can be used to fill in
+		preview Images"""
+		if self.objects is not None:
+			self.mainScrollArea.removeChild(self.objects)
+		self.objects = ObjectIconList(name='list', size=(200,1000))
+		self.objects.base_color = self.mainScrollArea.background_color
+		self.mainScrollArea.addChild(self.objects)
+
+	def setTextList(self):
+		"""Sets the mainScrollArea to contain a List that can be used to fill in
+		Object names/paths"""
+		if self.objects is not None:
+			self.mainScrollArea.removeChild(self.objects)
+		self.objects = widgets.ListBox(name='list')
+		self.objects.capture(self.listEntrySelected)
+		self.mainScrollArea.addChild(self.objects)
+
+	def _search(self):
+		self.search(self._searchfield.text)
+
+	def search(self, str):
+		results = []	
+			
+		# Format search terms
+		terms = [term.lower() for term in str.split()]
+		
+		# Search
+		if len(terms) > 0:
+			namespaces = self.engine.getModel().getNamespaces()
+			for namesp in namespaces:
+				objects = self.engine.getModel().getObjects(namesp)
+				for obj in objects:
+					doAppend = True
+					for term in terms:
+						if obj.getId().lower().find(term) < 0:
+							doAppend = False
+							break
+					if doAppend:
+						results.append(obj)
+		else:
+			results = None
+		
+		if self.mode == 'list':
+			self.fillTextList(results)
+		elif self.mode == 'preview':
+			self.fillPreviewList(results)
+
+	def fillTextList(self, objects=None):
+		if objects is None:
+			if self.namespaces.selected_item is None:
+				return
+			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
+		
+		class _ListItem:
+			def __init__( self, name, namespace ):
+				self.name = name
+				self.namespace = namespace
+			def __str__( self ):
+				return self.name
+			
+		
+		self.objects.items = [_ListItem(obj.getId(), obj.getNamespace()) for obj in objects]
+			
+		if not self.object:
+			if self.namespaces.selected_item:
+				self.objects.selected = 0
+				self.listEntrySelected()
+		else:
+			for i in range(0, len(self.objects.items)):
+				if self.objects.items[i].name != self.object.getId(): continue
+				if self.objects.items[i].namespace != self.object.getNamespace(): continue
+				
+				self.objects.selected = i
+				break
+				
+				
+		self.mainScrollArea.adaptLayout(False)
+		scrollY = (self.objects.real_font.getHeight() + 0) * self.objects.selected
+		self.mainScrollArea.real_widget.setVerticalScrollAmount(scrollY)
+
+	def listEntrySelected(self):
+		"""This function is used as callback for the TextList."""
+		if self.objects.selected_item:
+			object_id = self.objects.selected_item.name
+			namespace = self.objects.selected_item.namespace
+			obj = self.engine.getModel().getObject(object_id, namespace)
+			self.objectSelected(obj)
+
+	def fillPreviewList(self, objects=None):
+		self.objects.clear()
+		
+		if objects is None:
+			if self.namespaces.selected_item is None:
+				return
+			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
+		
+		for obj in objects:
+			image = self._getImage(obj)
+			if image is None:
+				print 'No image available for selected object'
+				image = ""
+
+			callback = tools.callbackWithArguments(self.objectSelected, obj)	
+			icon = ObjectIcon(callback=callback, image=image, text=unicode(obj.getId()))
+			self.objects.addChild(icon)
+			if obj == self.object:
+				icon.selected = True
+			
+		if not self.object:
+			if len(objects) > 0:
+				self.objectSelected(objects[0])
+				
+		self.mainScrollArea.adaptLayout(False)
+		self.mainScrollArea.real_widget.setVerticalScrollAmount(self.objects.selected_item.y)
+
+
+	def objectSelected(self, obj):
+		"""This is used as callback function to notify the editor that a new object has
+		been selected.
+		@param obj: fife.Object instance"""
+
+		self.setPreview(obj)
+		
+		events.onObjectSelected.send(sender=self, object=obj)
+
+		self.gui.adaptLayout(False)
+		
+	# Set preview image
+	def setPreview(self, object):
+		if not object: return
+		if self.object and object == self.object:
+			return
+			
+		self.object = object
+		self.scrollToObject(object)
+		self.preview.image = self._getImage(object)
+		height = self.preview.image.getHeight();
+		if height > 200: height = 200
+		self.preview.parent.max_height = height
+		
+	def scrollToObject(self, object):
+		# Select namespace
+		names = self.namespaces
+		if not names.selected_item: 
+			self.namespaces.selected = 0
+		
+		if names.selected_item != object.getNamespace():
+			for i in range(0, len(names.items)):
+				if names.items[i] == object.getNamespace():
+					self.namespaces.selected = i
+					break
+					
+		self.update()
+
+	def update_namespace(self):
+		
+		self.namespaces.items = self.engine.getModel().getNamespaces()
+		if not self.namespaces.selected_item:
+			self.namespaces.selected = 0
+		if self.mode == 'list':
+			self.setTextList()
+		elif self.mode == 'preview':
+			self.setImageList()
+		self.update()
+
+	def update(self):
+		if self.mode == 'list':
+			self.fillTextList()
+		elif self.mode == 'preview':
+			self.fillPreviewList()
+
+		self.gui.adaptLayout(False)
+
+	def _getImage(self, obj):
+		""" Returns an image for the given object.
+		@param: fife.Object for which an image is to be returned
+		@return: fife.GuiImage"""
+		visual = None
+		try:
+			visual = obj.get2dGfxVisual()
+		except:
+			print 'Visual Selection created for type without a visual?'
+			raise
+
+		# Try to find a usable image
+		index = visual.getStaticImageIndexByAngle(0)
+		image = None
+		# if no static image available, try default action
+		if index == -1:
+			action = obj.getDefaultAction()
+			if action:
+				animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(0)
+				animation = self.engine.getAnimationPool().getAnimation(animation_id)
+				image = animation.getFrameByTimestamp(0)
+				index = image.getPoolId()
+
+		# Construct the new GuiImage that is to be returned
+		if index != -1:
+			image = fife.GuiImage(index, self.engine.getImagePool())
+
+		return image
+
+
+	def show(self):
+		self.update_namespace()
+		self.gui.show()
+		self._showAction.setChecked(True)
+
+	def hide(self):
+		self.gui.setDocked(False)
+		self.gui.hide()
+		self._showAction.setChecked(False)
+		
+	def toggle(self):
+		if self.gui.isVisible() or self.gui.isDocked():
+			self.hide()
+		else:
+			self.show()