diff options
Diffstat (limited to 'ka_controller.py')
-rw-r--r-- | ka_controller.py | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/ka_controller.py b/ka_controller.py new file mode 100644 index 0000000..05ef161 --- /dev/null +++ b/ka_controller.py @@ -0,0 +1,357 @@ +# coding: UTF8 +# Copyright 2009 Thomas Jourdan +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import hashlib +import gtk +import gtk.glade +import cairo +import ka_debug +import model_population +import ka_incoming +import kandidtube +import ka_random +import ka_task + +class KandidController(object): + """ + inv: self.model is not None + inv: self._canvas is not None + """ + + def __init__(self, init_canvas): + """ + pre: init_canvas is not None + """ + self._canvas = init_canvas + self._tube = None + + # create to data model + self.model = model_population.KandidModel(12) + self.model.randomize() + self.incoming = ka_incoming.KandidIncoming(3) + + self.surface_cache = {} + + #Create a dictionary to connect events + events = { + 'on_breed_generation' : self.on_breed_generation, + 'on_random_generation' : self.on_random_generation, + 'on_flurry_value_changed' : self.on_flurry_value_changed, + 'on_decline_incoming' : self.on_decline_incoming, + 'on_accept_incoming' : self.on_accept_incoming, + 'delete_event' : gtk.main_quit } + for cell_index in range(self.model.size): + strix = str(cell_index) + key = 'on_drawingarea_#_expose'.replace('#', strix) + events[key] = self.on_drawingarea_expose + key = 'on_drawingarea_#_size_allocate'.replace('#', strix) + events[key] = self.on_drawingarea_size_allocate + key = 'on_fitness_#_value_changed'.replace('#', strix) + events[key] = self.on_fitness_value_changed + key = 'on_protozoon_popup_#'.replace('#', strix) + events[key] = self.on_protozoon_popup + key = 'on_publishprotozoon_activate_#'.replace('#', strix) + events[key] = self.on_publishprotozoon_activate + events['on_favorite_activate_' + strix] = self.on_favorite_activate + events['on_awfull_activate_' + strix] = self.on_awfull_activate + for cell_index in range(3): + strix = str(cell_index) + key = 'on_incomingarea_#_expose'.replace('#', strix) + events[key] = self.on_incomingarea_expose + key = 'on_incoming_#_popup'.replace('#', strix) + events[key] = self.on_incoming_popup + self._canvas.widgetTree.signal_autoconnect(events) + self._update_gui() + + def _update_model(self, in_model): + if in_model: + self.model = in_model + self.model._state = model_population.STATE_EVOLVED + self._update_gui() + + def _update_gui(self): + for cell_index in range(self.model.size): + strix = str(cell_index) + key = 'fitness_#'.replace('#', strix) + self._canvas.widgetTree.get_widget(key). \ + set_value(self.model.fitness[cell_index]) + self._canvas.widgetTree.get_widget('flurrySpinButton'). \ + set_value(self.model.flurry_rate) + + def _draw_from_cache(self, widget, cell_index): + if self.surface_cache.has_key(cell_index): + ka_debug.info('_draw_from_cache: ' + widget.name + ' ' + str(cell_index)) + ctx = self._create_context(widget) + ctx.set_operator(cairo.OPERATOR_SOURCE) + ctx.set_source_surface(self.surface_cache[cell_index]) + ctx.paint() + + def on_drawingarea_expose(self, widget, event): + """ Repaint image of a single protozoon inside population area. + pre: widget is not None + """ + # draw precalculated protozoon stored in the surface cache. + ka_debug.info('on_drawingarea_expose: ' + widget.name + ' ' + + str(widget.allocation.width) + + 'x' + str(widget.allocation.height)) + self._draw_from_cache(widget, _name_to_index(widget.name)) + + def on_drawingarea_size_allocate(self, widget, event): + """ New size for drawing area available. + pre: widget is not None + """ + ka_debug.info('on_drawingarea_size_allocate: ' + widget.name + ' ' + + str(widget.allocation.width) + + 'x' + str(widget.allocation.height)) + self._start_calculation([_name_to_index(widget.name)]) + + def _create_context(self, widget): + """ Create cairo context. + pre: widget is not None + """ + ctx = widget.window.cairo_create() + ctx.rectangle(0, 0, \ + widget.allocation.width, widget.allocation.height) + ctx.clip() + return ctx + + def on_fitness_value_changed(self, *args): + """ + pre: len(args) >= 1 + """ + ka_debug.info('on_fitness_value_changed %f [%s]' % + (args[0].get_value(), args[0].get_name())) + self.model.fitness[_name_to_index(args[0].get_name())] \ + = args[0].get_value() + + def on_breed_generation(self, *args): + if ka_task.is_completed(): + ka_debug.info('on_breed_generation entry') + ka_task.GeneratorTask(self.task_breed_generation, + self.on_model_completed).start() + ka_debug.info('on_breed_generation exit') + else: + ka_debug.info('on_breed_generation ignored') + + def on_random_generation(self, *args): + if ka_task.is_completed(): + ka_debug.info('on_random_generation entry') + ka_task.GeneratorTask(self.task_random_generation, + self.on_model_completed).start() + ka_debug.info('on_random_generation exit') + else: + ka_debug.info('on_random_generation ignored') + + def on_model_completed(self, *args): + """ + pre: len(args) == 1 + """ + ka_debug.info('on_model_completed entry') + for cell_index in args[0]: + self._canvas.widgetTree.get_widget('vbox_' + str(cell_index)). \ + set_sensitive(False) + self._start_calculation(args[0]) + ka_debug.info('on_model_completed exit') + + def _start_calculation(self, concerned): + """ + pre: len(concerned) > 0 + pre: forall(concerned, lambda x: 0 <= x <= self.model.size) + """ + for cell_index in concerned: + widget = self._canvas.widgetTree.get_widget('drawingarea_' + + str(cell_index)) + task = ka_task.GeneratorTask(self.task_draw, + self.on_image_completed) + task.start(self.model.protozoans[cell_index], cell_index, + widget.allocation.width, widget.allocation.height) + + def task_breed_generation(self, *args, **kwargs): + """ + pre: len(args) == 0 + """ + ka_debug.info('task_breed_generation entry') + new_indices = self.model.breed() + ka_debug.info('task_breed_generation exit') + return new_indices + + def task_random_generation(self, *args, **kwargs): + """ + pre: len(args) == 0 + """ + ka_debug.info('task_random_generation entry') + new_indices = self.model.random() + ka_debug.info('task_random_generation exit') + return new_indices + + def task_draw(self, *args, **kwargs): + """ + pre: len(args) == 4 + """ + protozoon, cell_index, width, height = args[0], args[1], args[2], args[3] + ka_debug.info('task_draw entry: ' + str(cell_index)) + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) + ctx = cairo.Context(surface) + protozoon.draw(ctx, width, height) + self.surface_cache[cell_index] = surface + ka_debug.info('task_draw exit: ' + str(cell_index)) + return cell_index + + def on_image_completed(self, *args): + ka_debug.info('on_image_completed: ' + str(args[0])) + cell_index = args[0] + widget = self._canvas.widgetTree.get_widget('drawingarea_' + + str(cell_index)) + self._draw_from_cache(widget, cell_index) + self._canvas.widgetTree.get_widget('vbox_' + str(cell_index)). \ + set_sensitive(True) + self._canvas.widgetTree.get_widget('fitness_' + str(cell_index)). \ + set_value(self.model.fitness[cell_index]) + + def on_flurry_value_changed(self, *args): + """ + pre: len(args) >= 1 + pre: 0 <= args[0].get_value() <= 9 + """ + ka_debug.info('on_flurry_value_changed [%s]' % args[0].get_value()) + ka_random.set_flurry(args[0].get_value()) + + def on_protozoon_popup(self, widget, event): + ka_debug.info('on_protozoon_popup: ' + widget.name) + self._show_popup(widget, event, \ + 'protozoon_menu_%u' % _name_to_index(widget.name)) + + def on_publishprotozoon_activate(self, *args): + """Publish single protozoon to all other buddies. + pre: len(args) >= 1 + """ + ka_debug.info('on_publishprotozoon_activate [%s]' % args[0].get_name()) + if self._tube: + proto = self.model.protozoans[_name_to_index(args[0].get_name())] + self._tube.publish_protozoon(model_population.to_buffer(proto)) + + def on_favorite_activate(self, *args): + """Set best ranking for this protozoon. + pre: len(args) >= 1 + """ + ka_debug.info('on_favorite_activate [%s]' % args[0].get_name()) + self.model.raise_fitness(_name_to_index(args[0].get_name())) + self._update_gui() + + def on_awfull_activate(self, *args): + """Set last ranking for this protozoon. + pre: len(args) >= 1 + """ + ka_debug.info('on_awfull_activate [%s]' % args[0].get_name()) + self.model.reduce_fitness(_name_to_index(args[0].get_name())) + self._update_gui() + + def on_received(self, code_type, code_element, code_md5): + """Update population or protozoon preview when received from others.""" + ka_debug.info('on_received: Received %u bytes, type: [%s] md5: [%s]' % \ + (len(code_element), code_type, code_md5)) + if hashlib.md5(code_element).hexdigest() == code_md5: + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + if code_type == kandidtube.SEND_POPULATION: + if self.is_overwrite_allowed: + self._update_model(model_population.from_buffer(code_element)) + self.model._state = model_population.STATE_EVOLVED + inbox_widget.queue_draw() + else: + ka_debug.info("I've already an evolved population, doing nothing") + elif code_type == kandidtube.SEND_PROTOZOON: + self.incoming.append_protozoon(model_population.from_buffer(code_element)) + inbox_widget.queue_draw() + else: + ka_debug.info('Somebody called me using an illegal type [%s]' \ + % code_type) + else: + ka_debug.info('Somebody called me with an corrupt data model') + + def on_new_tube(self, tube, is_initiator, get_buddy): + """Creates communication object and sends population + pre: tube > 0 + pre: get_buddy is not None + """ + self._tube = kandidtube.KandidTube(self, + tube, + is_initiator, + get_buddy) + +# Event handling for incoming protozoans + def on_incoming_popup(self, widget, event): + ka_debug.info('on_incoming_popup: ' + widget.name) + index = _name_to_index(widget.name) + self._show_popup(widget, event, 'incoming_menu_'+ str(index)) + + def _show_popup(self, widget, event, menu): + ka_debug.info('%s [%s]' % (menu, widget.name)) + popup_menu = self._canvas.widgetTree.get_widget(menu) + popup_menu.popup(None, None, None, event.button, event.time) + + def on_accept_incoming(self, menu_item): + ka_debug.info('on_accept_incoming [%s]' % menu_item.parent.name) + new_at = self.incoming.accept_protozoon(self.model, \ + _name_to_index(menu_item.parent.name)) + self._start_calculation([new_at]) +# self._update_gui() + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + inbox_widget.queue_draw() + + def on_decline_incoming(self, menu_item): + ka_debug.info('on_decline_incoming [%s]' % menu_item.parent.name) + self.incoming.decline_protozoon(_name_to_index(menu_item.parent.name)) + inbox_widget = self._canvas.widgetTree.get_widget('incomingBox') + inbox_widget.queue_draw() + + def on_incomingarea_expose(self, widget, event): + """ Repaint image of a single protozoon inside incoming area. + pre: widget is not None + """ + ka_debug.info('on_incomingarea_expose: ' + widget.name) + self.incoming.draw(_name_to_index(widget.name), \ + self._create_context(widget), \ + widget.allocation.width, widget.allocation.height) + +# data model and persistence + def is_overwrite_allowed(self): + """Preserve an already evolved population from over writing.""" + return self.model.is_overwrite_allowed() + + def serialize_model(self): + """Serialize population to a string buffer.""" + return model_population.to_buffer(self.model) + + def read_file(self, file_path): + """Delegate reading from journal to data model + pre: (file_path is not None) and (len(file_path) >= 1) + """ + self._update_model(model_population.read_file(file_path)) + + def write_file(self, file_path): + """Delegate writing data model to journal + pre: (file_path is not None) and (len(file_path) >= 1) + """ + model_population.write_file(file_path, self.model) + +def _name_to_index(name): + """Extract index from last part of a name. + Parts are separated by _. + pre: name[-1].isdigit() + post: __return__ >= 0 + """ + return int(name.rsplit('_')[-1]) |