comparison 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
comparison
equal deleted inserted replaced
377:fe6fb0e0ed23 378:64738befdf3b
1 # coding: utf-8
2
3 from fife.extensions import pychan
4 from fife.extensions.pychan import widgets, tools, attrs, internal
5 from fife.extensions.pychan.tools import callbackWithArguments
6 import scripts
7 import scripts.plugin as plugin
8 from scripts.events import *
9 from scripts.gui.action import Action
10 from fife import fife
11 from fife.fife import Color
12
13 # TODO:
14 # - Clean up code
15 # - Better event handling
16
17 _DEFAULT_BASE_COLOR = internal.DEFAULT_STYLE['default']['base_color']
18 _DEFAULT_SELECTION_COLOR = internal.DEFAULT_STYLE['default']['selection_color']
19 _DEFAULT_COLOR_STEP = Color(10, 10, 10)
20
21 class ObjectIcon(widgets.VBox):
22 """ The ObjectIcon is used to represent the object in the object selector.
23 """
24 ATTRIBUTES = widgets.VBox.ATTRIBUTES + [ attrs.Attr("text"), attrs.Attr("image"), attrs.BoolAttr("selected") ]
25
26 def __init__(self,callback,**kwargs):
27 super(ObjectIcon,self).__init__(**kwargs)
28
29 self.callback = callback
30
31 self.capture(self._mouseEntered, "mouseEntered")
32 self.capture(self._mouseExited, "mouseExited")
33 self.capture(self._mouseClicked, "mouseClicked")
34
35 vbox = widgets.VBox(padding=3)
36
37 # Icon
38 self.icon = widgets.Icon(**kwargs)
39 self.addChild(self.icon)
40
41 # Label
42 hbox = widgets.HBox(padding=1)
43 self.addChild(hbox)
44 self.label = widgets.Label(**kwargs)
45 hbox.addChild(self.label)
46
47 def _setText(self, text):
48 self.label.text = text
49
50 def _getText(self):
51 return self.label.text
52 text = property(_getText, _setText)
53
54 def _setImage(self, image):
55 self.icon.image = image
56
57 def _getImage(self):
58 return self.icon.image
59 image = property(_getImage, _setImage)
60
61 def _setSelected(self, enabled):
62 if isinstance(self.parent, ObjectIconList):
63 if enabled == True:
64 self.parent.selected_item = self
65 else:
66 if self.selected:
67 self.parent.selected_item = None
68
69 if self.selected:
70 self.base_color = _DEFAULT_SELECTION_COLOR
71 else:
72 self.base_color = _DEFAULT_BASE_COLOR
73
74 def _isSelected(self):
75 if isinstance(self.parent, ObjectIconList):
76 return self == self.parent.selected_item
77 return False
78 selected = property(_isSelected, _setSelected)
79
80 #--- Event handling ---#
81 def _mouseEntered(self, event):
82 self.base_color += _DEFAULT_COLOR_STEP
83
84 def _mouseExited(self, event):
85 self.base_color -= _DEFAULT_COLOR_STEP
86
87 def _mouseClicked(self, event):
88 self.selected = True
89 self.callback()
90
91 class ObjectIconList(widgets.VBox):
92 ATTRIBUTES = widgets.VBox.ATTRIBUTES
93
94 def __init__(self,**kwargs):
95 super(ObjectIconList, self).__init__(max_size=(5000,500000), **kwargs)
96 self.base_color = self.background_color
97
98 self.capture(self._keyPressed, "keyPressed")
99 #self.capture(self._keyPressed, "keyReleased")
100 self._selectedItem = None
101 self.is_focusable = True
102
103 def _keyPressed(self, event):
104 print "KeyEvent", event
105
106 def clear(self):
107 for c in reversed(self.children):
108 self.removeChild(c)
109
110 def _setSelectedItem(self, item):
111 if isinstance(item, ObjectIcon) or item is None:
112 if self._selectedItem is not None:
113 tmp = self._selectedItem
114 self._selectedItem = item
115 tmp.selected = False
116 else:
117 self._selectedItem = item
118 #if item is not None:
119 # item.selected = True
120
121 def _getSelectedItem(self):
122 return self._selectedItem
123 selected_item = property(_getSelectedItem, _setSelectedItem)
124
125 class ObjectSelector(plugin.Plugin):
126 """The ObjectSelector class offers a gui Widget that let's you select the object you
127 wish to use to in the editor.
128 @param engine: fife instance
129 @param map: fife.Map instance containing your map
130 @param selectNotify: callback function used to tell the editor you selected an object.
131 """
132 def __init__(self):
133 self.editor = None
134 self.engine = None
135 self.mode = 'list' # Other mode is 'preview'
136
137 self._enabled = False
138 self.object = None
139
140 def enable(self):
141 if self._enabled is True:
142 return
143
144 self.editor = scripts.editor.getEditor()
145 self.engine = self.editor.getEngine()
146
147 self._showAction = Action(u"Object selector", checkable=True)
148 scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
149
150 self.editor._tools_menu.addAction(self._showAction)
151
152 events.postMapShown.connect(self.update_namespace)
153 events.onObjectSelected.connect(self.setPreview)
154 events.onObjectsImported.connect(self.update_namespace)
155
156 self.buildGui()
157
158 def disable(self):
159 if self._enabled is False:
160 return
161
162 self.gui.hide()
163 self.removeAllChildren()
164
165 events.postMapShown.disconnect(self.update_namespace)
166 events.onObjectSelected.disconnect(self.setPreview)
167 events.onObjectsImported.disconnect(self.update_namespace)
168
169 self.editor._tools_menu.removeAction(self._showAction)
170
171 def isEnabled(self):
172 return self._enabled;
173
174 def getName(self):
175 return "Object selector"
176
177
178 def buildGui(self):
179 self.gui = pychan.loadXML('gui/objectselector.xml')
180
181 # Add search field
182 self._searchfield = self.gui.findChild(name="searchField")
183 self._searchfield.capture(self._search)
184 self._searchfield.capture(self._search, "keyPressed")
185 self.gui.findChild(name="searchButton").capture(self._search)
186
187 # Add the drop down with list of namespaces
188 self.namespaces = self.gui.findChild(name="namespaceDropdown")
189 self.namespaces.items = self.engine.getModel().getNamespaces()
190 self.namespaces.selected = 0
191
192 # TODO: Replace with SelectionEvent, once pychan supports it
193 self.namespaces.capture(self.update_namespace, "action")
194 self.namespaces.capture(self.update_namespace, "mouseWheelMovedUp")
195 self.namespaces.capture(self.update_namespace, "mouseWheelMovedDown")
196 self.namespaces.capture(self.update_namespace, "keyReleased")
197
198 # Object list
199 self.mainScrollArea = self.gui.findChild(name="mainScrollArea")
200 self.objects = None
201 if self.mode == 'list':
202 self.setTextList()
203 else: # Assuming self.mode is 'preview'
204 self.setImageList()
205
206 # Action buttons
207 self.gui.findChild(name="toggleModeButton").capture(self.toggleMode)
208 self.gui.findChild(name="closeButton").capture(self.hide)
209
210 # Preview area
211 self.gui.findChild(name="previewScrollArea").background_color = self.gui.base_color
212 self.preview = self.gui.findChild(name="previewIcon")
213
214
215 def toggleMode(self):
216 if self.mode == 'list':
217 self.setImageList()
218 self.mode = 'preview'
219 elif self.mode == 'preview':
220 self.setTextList()
221 self.mode = 'list'
222 self.update()
223
224
225 def setImageList(self):
226 """Sets the mainScrollArea to contain a Vbox that can be used to fill in
227 preview Images"""
228 if self.objects is not None:
229 self.mainScrollArea.removeChild(self.objects)
230 self.objects = ObjectIconList(name='list', size=(200,1000))
231 self.objects.base_color = self.mainScrollArea.background_color
232 self.mainScrollArea.addChild(self.objects)
233
234 def setTextList(self):
235 """Sets the mainScrollArea to contain a List that can be used to fill in
236 Object names/paths"""
237 if self.objects is not None:
238 self.mainScrollArea.removeChild(self.objects)
239 self.objects = widgets.ListBox(name='list')
240 self.objects.capture(self.listEntrySelected)
241 self.mainScrollArea.addChild(self.objects)
242
243 def _search(self):
244 self.search(self._searchfield.text)
245
246 def search(self, str):
247 results = []
248
249 # Format search terms
250 terms = [term.lower() for term in str.split()]
251
252 # Search
253 if len(terms) > 0:
254 namespaces = self.engine.getModel().getNamespaces()
255 for namesp in namespaces:
256 objects = self.engine.getModel().getObjects(namesp)
257 for obj in objects:
258 doAppend = True
259 for term in terms:
260 if obj.getId().lower().find(term) < 0:
261 doAppend = False
262 break
263 if doAppend:
264 results.append(obj)
265 else:
266 results = None
267
268 if self.mode == 'list':
269 self.fillTextList(results)
270 elif self.mode == 'preview':
271 self.fillPreviewList(results)
272
273 def fillTextList(self, objects=None):
274 if objects is None:
275 if self.namespaces.selected_item is None:
276 return
277 objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
278
279 class _ListItem:
280 def __init__( self, name, namespace ):
281 self.name = name
282 self.namespace = namespace
283 def __str__( self ):
284 return self.name
285
286
287 self.objects.items = [_ListItem(obj.getId(), obj.getNamespace()) for obj in objects]
288
289 if not self.object:
290 if self.namespaces.selected_item:
291 self.objects.selected = 0
292 self.listEntrySelected()
293 else:
294 for i in range(0, len(self.objects.items)):
295 if self.objects.items[i].name != self.object.getId(): continue
296 if self.objects.items[i].namespace != self.object.getNamespace(): continue
297
298 self.objects.selected = i
299 break
300
301
302 self.mainScrollArea.adaptLayout(False)
303 scrollY = (self.objects.real_font.getHeight() + 0) * self.objects.selected
304 self.mainScrollArea.real_widget.setVerticalScrollAmount(scrollY)
305
306 def listEntrySelected(self):
307 """This function is used as callback for the TextList."""
308 if self.objects.selected_item:
309 object_id = self.objects.selected_item.name
310 namespace = self.objects.selected_item.namespace
311 obj = self.engine.getModel().getObject(object_id, namespace)
312 self.objectSelected(obj)
313
314 def fillPreviewList(self, objects=None):
315 self.objects.clear()
316
317 if objects is None:
318 if self.namespaces.selected_item is None:
319 return
320 objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
321
322 for obj in objects:
323 image = self._getImage(obj)
324 if image is None:
325 print 'No image available for selected object'
326 image = ""
327
328 callback = tools.callbackWithArguments(self.objectSelected, obj)
329 icon = ObjectIcon(callback=callback, image=image, text=unicode(obj.getId()))
330 self.objects.addChild(icon)
331 if obj == self.object:
332 icon.selected = True
333
334 if not self.object:
335 if len(objects) > 0:
336 self.objectSelected(objects[0])
337
338 self.mainScrollArea.adaptLayout(False)
339 self.mainScrollArea.real_widget.setVerticalScrollAmount(self.objects.selected_item.y)
340
341
342 def objectSelected(self, obj):
343 """This is used as callback function to notify the editor that a new object has
344 been selected.
345 @param obj: fife.Object instance"""
346
347 self.setPreview(obj)
348
349 events.onObjectSelected.send(sender=self, object=obj)
350
351 self.gui.adaptLayout(False)
352
353 # Set preview image
354 def setPreview(self, object):
355 if not object: return
356 if self.object and object == self.object:
357 return
358
359 self.object = object
360 self.scrollToObject(object)
361 self.preview.image = self._getImage(object)
362 height = self.preview.image.getHeight();
363 if height > 200: height = 200
364 self.preview.parent.max_height = height
365
366 def scrollToObject(self, object):
367 # Select namespace
368 names = self.namespaces
369 if not names.selected_item:
370 self.namespaces.selected = 0
371
372 if names.selected_item != object.getNamespace():
373 for i in range(0, len(names.items)):
374 if names.items[i] == object.getNamespace():
375 self.namespaces.selected = i
376 break
377
378 self.update()
379
380 def update_namespace(self):
381
382 self.namespaces.items = self.engine.getModel().getNamespaces()
383 if not self.namespaces.selected_item:
384 self.namespaces.selected = 0
385 if self.mode == 'list':
386 self.setTextList()
387 elif self.mode == 'preview':
388 self.setImageList()
389 self.update()
390
391 def update(self):
392 if self.mode == 'list':
393 self.fillTextList()
394 elif self.mode == 'preview':
395 self.fillPreviewList()
396
397 self.gui.adaptLayout(False)
398
399 def _getImage(self, obj):
400 """ Returns an image for the given object.
401 @param: fife.Object for which an image is to be returned
402 @return: fife.GuiImage"""
403 visual = None
404 try:
405 visual = obj.get2dGfxVisual()
406 except:
407 print 'Visual Selection created for type without a visual?'
408 raise
409
410 # Try to find a usable image
411 index = visual.getStaticImageIndexByAngle(0)
412 image = None
413 # if no static image available, try default action
414 if index == -1:
415 action = obj.getDefaultAction()
416 if action:
417 animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(0)
418 animation = self.engine.getAnimationPool().getAnimation(animation_id)
419 image = animation.getFrameByTimestamp(0)
420 index = image.getPoolId()
421
422 # Construct the new GuiImage that is to be returned
423 if index != -1:
424 image = fife.GuiImage(index, self.engine.getImagePool())
425
426 return image
427
428
429 def show(self):
430 self.update_namespace()
431 self.gui.show()
432 self._showAction.setChecked(True)
433
434 def hide(self):
435 self.gui.setDocked(False)
436 self.gui.hide()
437 self._showAction.setChecked(False)
438
439 def toggle(self):
440 if self.gui.isVisible() or self.gui.isDocked():
441 self.hide()
442 else:
443 self.show()