Mercurial > fife-parpg
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() |