def __init__(self):
    """The gui consists of a serie of controls to choose the number of rows,
    the number of columns, and the generating algorithm, a drawing area used to
    show the progress of the maze generation, and a couple of buttons used to
    clear the current maze, and to start the generating process.

    """
    builder = gtk.Builder()
    builder.add_from_file(GUI_CONF)
    builder.connect_signals(self)

    self.window = builder.get_object('window')
    self.darea = builder.get_object('darea')
    self._rows = builder.get_object('rows')
    self._columns = builder.get_object('columns')
    self._generators = builder.get_object('generators')
    self._solvers = builder.get_object('solvers')
    self._generate = builder.get_object('generate')
    self._solve = builder.get_object('solve')
    self._show_evoltuion = builder.get_object('show_evolution')
    self.widgets = [self._rows, self._columns, self._generators,
                    self._solvers, self._generate, self._solve]

    combobox_init(self._generators, generators.keys())
    combobox_init(self._solvers, solvers.keys())

    self.maze = Maze(self.rows, self.columns)
  def generate_clicked_cb(self, button):
    """Start the maze generator process.

    """
    self.maze = Maze(self.rows, self.columns)
    self.draw_maze(self.cr)
    process_fun = self.process(generators[self.generator](self.maze))
    gobject.idle_add(process_fun.next)
  def value_changed_cb(self, widget):
    """Create a new maze, update the grid and then redraw the scene.

    """
    (width, height) = self.darea_size
    self.maze = Maze(self.rows, self.columns)
    (self.grid, self.delta) = \
        create_grid_and_delta(width, height, self.rows, self.columns)
    self.draw_maze(self.cr)
    self.window.queue_draw()
class Gui(object):
  def __init__(self):
    """The gui consists of a serie of controls to choose the number of rows,
    the number of columns, and the generating algorithm, a drawing area used to
    show the progress of the maze generation, and a couple of buttons used to
    clear the current maze, and to start the generating process.

    """
    builder = gtk.Builder()
    builder.add_from_file(GUI_CONF)
    builder.connect_signals(self)

    self.window = builder.get_object('window')
    self.darea = builder.get_object('darea')
    self._rows = builder.get_object('rows')
    self._columns = builder.get_object('columns')
    self._generators = builder.get_object('generators')
    self._solvers = builder.get_object('solvers')
    self._generate = builder.get_object('generate')
    self._solve = builder.get_object('solve')
    self._show_evoltuion = builder.get_object('show_evolution')
    self.widgets = [self._rows, self._columns, self._generators,
                    self._solvers, self._generate, self._solve]

    combobox_init(self._generators, generators.keys())
    combobox_init(self._solvers, solvers.keys())

    self.maze = Maze(self.rows, self.columns)

  def delete_cb(self, widget, event):
    """Quit the gtk main loop.

    """
    gtk.main_quit()

  def value_changed_cb(self, widget):
    """Create a new maze, update the grid and then redraw the scene.

    """
    (width, height) = self.darea_size
    self.maze = Maze(self.rows, self.columns)
    (self.grid, self.delta) = \
        create_grid_and_delta(width, height, self.rows, self.columns)
    self.draw_maze(self.cr)
    self.window.queue_draw()

  def configure_cb(self, widget, event):
    """Each time the drawing area is reconfigured, we store a grid used to map
    the cells of the maze into the device space. We need also to inizialize a
    cairo surface which will be used for all the drawing actions.

    """
    (_, _, width, height) = widget.get_allocation()
    self.darea_size = (width, height)

    self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    self.cr = cairo.Context(self.surface)

    (self.grid, self.delta) = \
        create_grid_and_delta(width, height, self.rows, self.columns)
    self.draw_maze(self.cr)

    return True

  def expose_cb(self, widget, event):
    """Paint the internal cairo surface, over the exposed area.

    """
    cr = widget.window.cairo_create()
    cr.rectangle(event.area.x, event.area.y,
                 event.area.width, event.area.height)
    cr.clip()
    cr.set_source_surface(self.surface, 0, 0)
    cr.paint()
    return False

  def generate_clicked_cb(self, button):
    """Start the maze generator process.

    """
    self.maze = Maze(self.rows, self.columns)
    self.draw_maze(self.cr)
    process_fun = self.process(generators[self.generator](self.maze))
    gobject.idle_add(process_fun.next)

  def solve_clicked_cb(self, button):
    """Start the maze solver process.

    """
    self.maze.reset()
    self.draw_maze(self.cr)
    process_fun = self.process(solvers[self.solver](self.maze))
    gobject.idle_add(process_fun.next)

  def process(self, maze_gen):
    """Wrapper of the maze generator/solver functions.
    Each time a new step is done, we redraw the involved cells.
    All the widgets are disabled durint the process.

    """
    for item in self.widgets:
      item.set_sensitive(False)

    while maze_gen.next():
      if self._show_evoltuion.get_active():
        self.draw_modified(self.cr)
      yield True
    if not self._show_evoltuion.get_active():
      self.draw_maze(self.cr)

    for item in self.widgets:
      item.set_sensitive(True)

    yield False

  def draw_line(self, cr, x1, y1, x2, y2):
    """Draw a straight line connecting the given coordinates.

    """
    cr.move_to(x1, y1)
    cr.line_to(x2, y2)
    cr.stroke()

  def draw_cell(self, cr, i, j):
    """Draw a cell of the maze depending on the kind of cell (start point, end
    point..) and its walls.

    """
    cell = self.maze[(i, j)]
    delta = self.delta
    (x, y) = self.grid[i][j]
    if cell.start: cr.set_source_rgb(1, 0, 0)
    elif cell.end: cr.set_source_rgb(0, 1, 0)
    elif cell.active: cr.set_source_rgb(1, 0, 0)
    elif cell.backward: cr.set_source_rgb(0.8, 0.8, 0.8)
    else: cr.set_source_rgb(1, 1, 1)
    cr.rectangle(x, y, delta, delta)
    cr.fill()

    cr.set_source_rgb(0, 0, 0)
    if cell.walls[NORTH]:
      self.draw_line(cr, x, y, x + delta, y)
    if cell.walls[EAST]:
      self.draw_line(cr, x + delta, y, x + delta, y + delta)
    if cell.walls[SOUTH]:
      self.draw_line(cr, x + delta, y + delta, x, y + delta)
    if cell.walls[WEST]:
      self.draw_line(cr, x, y + delta, x, y)

    self.darea.window.invalidate_rect((x, y, delta, delta), True)

  def draw_maze(self, cr):
    """Wrapper which invoke the draw_cell on each cell of the maze.

    """
    (width, height) = self.darea_size
    self.cr.set_source_rgb(1, 1, 1)
    self.cr.rectangle(0, 0, width, height)
    self.cr.fill()

    (rows, columns) = self.maze.size
    for i in xrange(rows):
      for j in xrange(columns):
        self.draw_cell(cr, i, j)

  def draw_modified(self, cr):
    """Wrapper which invoke the draw_cell on each modified cell.

    """
    while self.maze.modified:
      (i, j) = self.maze.modified.pop()
      self.draw_cell(cr, i, j)

  def mainloop(self):
    """Wrapper to gtk mainloop.

    """
    gtk.main()

  @property
  def rows(self):
    """Return the value of the spinbutton controlling the number of rows.

    """
    return self._rows.get_value_as_int()

  @property
  def columns(self):
    """Return the value of the spinbutton controlling the number of columns.

    """
    return self._columns.get_value_as_int()

  @property
  def generator(self):
    """Return the active text of the combobox controlling the generator
    algorithm.

    """
    return self._generators.get_active_text()

  @property
  def solver(self):
    """Return the active text of the combobox controlling the solver
    algorithm.

    """
    return self._solvers.get_active_text()