Ejemplo n.º 1
0
 def __init__(self, parent, proto_board, show_pwr_gnd_pins):
     """
 |proto_board|: the proto board to visualize.
 |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to
     connect to a power supply.
 """
     Frame.__init__(self, parent, background=BACKGROUND_COLOR)
     self._parent = parent
     self._parent.title(WINDOW_TITLE)
     self._parent.resizable(0, 0)
     self._proto_board = proto_board
     self._show_pwr_gnd_pins = show_pwr_gnd_pins
     self._canvas = Canvas(self,
                           width=WIDTH,
                           height=HEIGHT,
                           background=BACKGROUND_COLOR)
     self._tooltip_helper = Tooltip_Helper(self._canvas)
     self._wire_parts = {}
     # state for outline highlighing
     self._piece_outline_id = None
     self._wire_outline_ids = []
     self._piece_highlight = lambda labels: None
     self._wire_highlight = lambda label: None
     self._setup_bindings()
     self._setup_menu()
     self._draw_proto_board()
Ejemplo n.º 2
0
 def __init__(self, parent, proto_board, show_pwr_gnd_pins):
   """
   |proto_board|: the proto board to visualize.
   |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to
       connect to a power supply.
   """
   Frame.__init__(self, parent, background=BACKGROUND_COLOR)
   self._parent = parent
   self._parent.title(WINDOW_TITLE)
   self._parent.resizable(0, 0)
   self._proto_board = proto_board
   self._show_pwr_gnd_pins = show_pwr_gnd_pins
   self._canvas = Canvas(self, width=WIDTH, height=HEIGHT,
       background=BACKGROUND_COLOR)
   self._tooltip_helper = Tooltip_Helper(self._canvas)
   self._wire_parts = {}
   # state for outline highlighing
   self._piece_outline_id = None
   self._wire_outline_ids = []
   self._piece_highlight = lambda labels: None
   self._wire_highlight = lambda label: None
   self._setup_bindings()
   self._setup_menu()
   self._draw_proto_board()
Ejemplo n.º 3
0
class Proto_Board_Visualizer(Frame):
    """
  Tk Frame to visualize Proto boards.
  """
    def __init__(self, parent, proto_board, show_pwr_gnd_pins):
        """
    |proto_board|: the proto board to visualize.
    |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to
        connect to a power supply.
    """
        Frame.__init__(self, parent, background=BACKGROUND_COLOR)
        self._parent = parent
        self._parent.title(WINDOW_TITLE)
        self._parent.resizable(0, 0)
        self._proto_board = proto_board
        self._show_pwr_gnd_pins = show_pwr_gnd_pins
        self._canvas = Canvas(self,
                              width=WIDTH,
                              height=HEIGHT,
                              background=BACKGROUND_COLOR)
        self._tooltip_helper = Tooltip_Helper(self._canvas)
        self._wire_parts = {}
        # state for outline highlighing
        self._piece_outline_id = None
        self._wire_outline_ids = []
        self._piece_highlight = lambda labels: None
        self._wire_highlight = lambda label: None
        self._setup_bindings()
        self._setup_menu()
        self._draw_proto_board()

    def _point_inside_piece(self, piece, x, y):
        """
    Returns True if the point (|x|, |y|) is on the gien |piece|.
    """
        r1, c1, r2, c2 = piece.bbox()
        x1, y1 = self._rc_to_xy((r1, c1))
        x2, y2 = self._rc_to_xy((r2, c2))
        return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE

    def _maybe_show_tooltip(self, event):
        """
    Shows a tooltip of the respective node if the cursor is on a wire or a valid
        location on the proto board, or the respective piece label if the cursor
        is on a piece.
    """
        # check if cursor is on a wire
        item = self._canvas.find_closest(event.x, event.y)[0]
        if item in self._wire_parts:
            self._wire_highlight(self._wire_parts[item])
            return
        # check if cursor is on a piece
        for piece in self._proto_board.get_pieces():
            if self._point_inside_piece(piece, event.x, event.y):
                self._tooltip_helper.show_tooltip(
                    event.x,
                    event.y,
                    'ID: %s' % piece.label,
                    background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND)
                self._piece_highlight(
                    piece.labels_at((event.x, event.y),
                                    self._rc_to_xy(piece.top_left_loc)))
                return
        self._piece_highlight([])
        # check if cursor is on a valid proto board location
        loc = self._xy_to_rc(event.x, event.y)
        if loc:
            node = self._proto_board.node_for(loc)
            if node:
                self._wire_highlight(node)
                return
        self._wire_highlight(None)
        # if none of the above, remove previous tooltip, if any
        self._tooltip_helper.hide_tooltip()

    def _setup_bindings(self):
        """
    Sets up event bindings.
    """
        self._canvas.bind('<Motion>', self._maybe_show_tooltip)

    def _rc_to_xy(self, loc):
        """
    Returns the top left corner of the connector located at row |r| column |c|.
    """
        r, c = loc
        x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING
        y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + (
            num_vertical_separators(r) *
            (VERTICAL_SEPARATION - CONNECTOR_SPACING))
        return x, y

    def _xy_to_rc(self, x, y):
        """
    Returns the row and column of the valid location on the proto board
        containing the point (|x|, |y|), or None if no such location exists.
    """
        for r in xrange(PROTO_BOARD_HEIGHT):
            for c in xrange(PROTO_BOARD_WIDTH):
                if valid_loc((r, c)):
                    x1, y1 = self._rc_to_xy((r, c))
                    x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE
                    if x1 <= x <= x2 and y1 <= y <= y2:
                        return r, c
        return None

    def _draw_connector(self,
                        x,
                        y,
                        fill=CONNECTOR_COLOR,
                        outline=CONNECTOR_COLOR):
        """
    Draws a connector at the given coordinate and with the given colors.
    """
        self._canvas.create_rectangle(x,
                                      y,
                                      x + CONNECTOR_SIZE,
                                      y + CONNECTOR_SIZE,
                                      fill=fill,
                                      outline=outline)

    def _draw_connectors(self):
        """
    Draws the connectors on the proto board.
    """
        for r in ROWS:
            for c in COLUMNS:
                if valid_loc((r, c)):
                    self._draw_connector(*self._rc_to_xy((r, c)))

    def _draw_bus_line(self, y, color):
        """
    Draws a bus line at the given horizontal position |y| and with the given
        |color|.
    """
        x_1 = self._rc_to_xy((0, 1))[0]
        x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0]
        self._canvas.create_line(x_1, y, x_2, y, fill=color)

    def _draw_bus_lines(self):
        """
    Draws all four bus lines on the proto board.
    """
        offset = 10
        self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR)
        self._draw_bus_line(
            self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset,
            POSITIVE_COLOR)
        self._draw_bus_line(
            self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - (offset),
            NEGATIVE_COLOR)
        self._draw_bus_line(
            self._rc_to_xy(
                (PROTO_BOARD_HEIGHT - 1, 0))[1] + (CONNECTOR_SIZE + offset),
            POSITIVE_COLOR)

    def _draw_labels(self):
        """
    Draws the row and column labels.
    """
        # row labels
        row_labels = dict(
            zip(range(11, 1, -1),
                ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']))
        for r in filter(lambda r: r in row_labels, ROWS):
            self._canvas.create_text(self._rc_to_xy((r, -1)),
                                     text=row_labels[r])
            x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH))
            self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r])
        # columns labels
        h_offset = 2
        v_offset = 10
        for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS):
            x_1, y_1 = self._rc_to_xy((2, c))
            self._canvas.create_text(x_1 + h_offset,
                                     y_1 - v_offset,
                                     text=(c + 1))
            x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c))
            self._canvas.create_text(x_2 + h_offset,
                                     y_2 + CONNECTOR_SIZE + v_offset,
                                     text=(c + 1))

    def _draw_gnd_pwr_pins(self):
        """
    Draws pins to show the ground and power rails.
    """
        # pin positions
        g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3))
        p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3))
        # big rectangles
        large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING
        small_h_offset = 2
        large_v_offset = 7
        small_v_offset = 3
        self._canvas.create_rectangle(g_x - small_h_offset,
                                      g_y - large_v_offset,
                                      g_x + large_h_offset,
                                      g_y + CONNECTOR_SIZE + small_v_offset,
                                      fill=NEGATIVE_COLOR)
        self._canvas.create_rectangle(p_x - small_h_offset,
                                      p_y - small_v_offset,
                                      p_x + large_h_offset,
                                      p_y + CONNECTOR_SIZE + large_v_offset,
                                      fill=POSITIVE_COLOR)
        # connectors
        self._draw_connector(g_x, g_y, outline='black')
        self._draw_connector(p_x, p_y, outline='black')
        # text
        text_v_offset = 2
        text_h_offset = 17
        self._canvas.create_text(g_x + text_h_offset,
                                 g_y - text_v_offset,
                                 text='gnd',
                                 fill='white')
        self._canvas.create_text(p_x + text_h_offset,
                                 p_y + text_v_offset,
                                 text='+10',
                                 fill='white')

    def _draw_piece(self, piece):
        """
    Draws the given circuit |piece| on the canvas.
    """
        piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc))

    def _draw_pieces(self):
        """
    Draws all the pieces on the proto board.
    """
        for piece in self._proto_board.get_pieces():
            self._draw_piece(piece)

    def _draw_wire(self, wire):
        """
    Draws the given |wire| on the canvas.
    """
        x_1, y_1 = self._rc_to_xy(wire.loc_1)
        x_2, y_2 = self._rc_to_xy(wire.loc_2)
        length = int(ceil(wire.length()))
        fill = 'white'
        if 1 < length < 10:
            fill = WIRE_COLORS[length]
        elif 10 <= length < 50:
            fill = WIRE_COLORS[(length + 9) / 10]

        def draw_wire(parts=None):
            if parts:
                for part in parts:
                    self._canvas.delete(part)
                    del self._wire_parts[part]
            new_parts = [
                self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
                                         y_1 + CONNECTOR_SIZE / 2,
                                         x_2 + CONNECTOR_SIZE / 2,
                                         y_2 + CONNECTOR_SIZE / 2,
                                         fill=WIRE_OUTLINE,
                                         width=7,
                                         capstyle='round'),
                self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
                                         y_1 + CONNECTOR_SIZE / 2,
                                         x_2 + CONNECTOR_SIZE / 2,
                                         y_2 + CONNECTOR_SIZE / 2,
                                         fill=fill,
                                         width=3,
                                         capstyle='round')
            ]
            for part in new_parts:
                self._canvas.tag_bind(part, '<Button-1>',
                                      lambda event: draw_wire(new_parts))
                self._wire_parts[part] = wire.node

        draw_wire()

    def _draw_wires(self):
        """
    Draws all the wires on the proto board.
    """
        for wire in sorted(self._proto_board.get_wires(),
                           key=lambda wire: -wire.length()):
            self._draw_wire(wire)

    def _redraw_wires(self):
        """
    Erases then redraws the wires on the protoboard.
    """
        for part in self._wire_parts:
            self._canvas.delete(part)
        self._wire_parts.clear()
        self._draw_wires()

    def _draw_proto_board(self):
        """
    Draws the given |proto_board|.
    """
        self._draw_connectors()
        self._draw_bus_lines()
        self._draw_labels()
        self._draw_pieces()
        self._draw_wires()
        if self._show_pwr_gnd_pins:
            self._draw_gnd_pwr_pins()
        self._canvas.pack()
        self.pack()

    def _wire_to_cmax_str(self, wire):
        """
    Returns a CMax representation (when saved in a file) of the given |wire|.
    """
        c1, r1 = loc_to_cmax_rep(wire.loc_1)
        c2, r2 = loc_to_cmax_rep(wire.loc_2)
        return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2)

    def _to_cmax_str(self):
        """
    Returns a string CMax representation of the proto board we are visualizing.
    """
        # header
        lines = ['#CMax circuit']
        # wires
        for wire in self._proto_board.get_wires():
            lines.append(self._wire_to_cmax_str(wire))
        # circuit pieces
        for piece in self._proto_board.get_pieces():
            cmax_str = piece.to_cmax_str()
            if cmax_str:
                lines.append(cmax_str)
        # power and ground pins
        if self._show_pwr_gnd_pins:
            lines.append('+10: (61,20)')
            lines.append('gnd: (61,19)')
        return '\n'.join(lines)

    def _save_as_cmax_file(self):
        """
    Presents a dialog box that will save the proto board we are visualizing as a
        CMax file.
    """
        file_name = asksaveasfilename(title='Save as CMax file ...',
                                      filetypes=[('CMax files',
                                                  CMAX_FILE_EXTENSION)])
        if file_name and not file_name.endswith(CMAX_FILE_EXTENSION):
            file_name += CMAX_FILE_EXTENSION
        if file_name:
            save_file = open(file_name, 'w')
            save_file.write(self._to_cmax_str())
            save_file.close()

    def _setup_menu(self):
        """
    Sets up a menu that lets the user save the proto board we are visualizing as
        a CMax file.
    """
        menu = Menu(self._parent, tearoff=0)
        save_menu = Menu(menu, tearoff=0)
        save_menu.add_command(label='Save as CMax file',
                              command=self._save_as_cmax_file)
        menu.add_cascade(label='File', menu=save_menu)
        edit_menu = Menu(menu, tearoff=0)
        edit_menu.add_command(label='Redraw wires', command=self._redraw_wires)
        menu.add_cascade(label='Edit', menu=edit_menu)
        self._parent.config(menu=menu)

    def outline_piece_from_label(self, label):
        """
    Draws the appropriate outline for the circuit piece with the given |label|.
    """
        # try block in case canvas is gone with someone still calling this method
        try:
            self._canvas.delete(self._piece_outline_id)
            for piece in self._proto_board.get_pieces():
                if label in piece.label.split(','):
                    self._piece_outline_id = piece.outline_label(
                        self._canvas, self._rc_to_xy(piece.top_left_loc),
                        label)
                    return
        except:
            pass

    def outline_wires_from_label(self, label):
        """
    Draws outlines on the wires that have the given |label|.
    """
        # try block in case canvas is gone with someone still calling this method
        try:
            for part in self._wire_outline_ids:
                self._canvas.delete(part)
                del self._wire_parts[part]
            self._wire_outline_ids = []
            for wire in self._proto_board.get_wires():
                if wire.node == label:
                    x_1, y_1 = self._rc_to_xy(wire.loc_1)
                    x_2, y_2 = self._rc_to_xy(wire.loc_2)
                    self._wire_outline_ids.extend([
                        self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
                                                 y_1 + CONNECTOR_SIZE / 2,
                                                 x_2 + CONNECTOR_SIZE / 2,
                                                 y_2 + CONNECTOR_SIZE / 2,
                                                 fill='black',
                                                 width=7,
                                                 capstyle='round'),
                        self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
                                                 y_1 + CONNECTOR_SIZE / 2,
                                                 x_2 + CONNECTOR_SIZE / 2,
                                                 y_2 + CONNECTOR_SIZE / 2,
                                                 fill='cyan',
                                                 width=3,
                                                 capstyle='round')
                    ])
                    for part in self._wire_outline_ids:
                        self._wire_parts[part] = label
        except:
            pass

    def set_piece_highlight(self, f):
        """
    Resets the piece highlighting function to |f|.
    """
        self._piece_highlight = f

    def set_wire_highlight(self, f):
        """
    Resets the wire highlighing function to |f|.
    """
        def g(label):
            f(label)
            self.outline_wires_from_label(label)

        self._wire_highlight = g
Ejemplo n.º 4
0
class Proto_Board_Visualizer(Frame):
  """
  Tk Frame to visualize Proto boards.
  """
  def __init__(self, parent, proto_board, show_pwr_gnd_pins):
    """
    |proto_board|: the proto board to visualize.
    |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to
        connect to a power supply.
    """
    Frame.__init__(self, parent, background=BACKGROUND_COLOR)
    self._parent = parent
    self._parent.title(WINDOW_TITLE)
    self._parent.resizable(0, 0)
    self._proto_board = proto_board
    self._show_pwr_gnd_pins = show_pwr_gnd_pins
    self._canvas = Canvas(self, width=WIDTH, height=HEIGHT,
        background=BACKGROUND_COLOR)
    self._tooltip_helper = Tooltip_Helper(self._canvas)
    self._wire_parts = {}
    # state for outline highlighing
    self._piece_outline_id = None
    self._wire_outline_ids = []
    self._piece_highlight = lambda labels: None
    self._wire_highlight = lambda label: None
    self._setup_bindings()
    self._setup_menu()
    self._draw_proto_board()
  def _point_inside_piece(self, piece, x, y):
    """
    Returns True if the point (|x|, |y|) is on the gien |piece|.
    """
    r1, c1, r2, c2 = piece.bbox()
    x1, y1 = self._rc_to_xy((r1, c1))
    x2, y2 = self._rc_to_xy((r2, c2))
    return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE
  def _maybe_show_tooltip(self, event):
    """
    Shows a tooltip of the respective node if the cursor is on a wire or a valid
        location on the proto board, or the respective piece label if the cursor
        is on a piece.
    """
    # check if cursor is on a wire
    item = self._canvas.find_closest(event.x, event.y)[0]
    if item in self._wire_parts:
      self._wire_highlight(self._wire_parts[item])
      return
    # check if cursor is on a piece
    for piece in self._proto_board.get_pieces():
      if self._point_inside_piece(piece, event.x, event.y):
        self._tooltip_helper.show_tooltip(event.x, event.y,
            'ID: %s' % piece.label,
            background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND)
        self._piece_highlight(piece.labels_at((event.x, event.y),
            self._rc_to_xy(piece.top_left_loc)))
        return
    self._piece_highlight([])
    # check if cursor is on a valid proto board location
    loc = self._xy_to_rc(event.x, event.y)
    if loc:
      node = self._proto_board.node_for(loc)
      if node:
        self._wire_highlight(node)
        return
    self._wire_highlight(None)
    # if none of the above, remove previous tooltip, if any
    self._tooltip_helper.hide_tooltip()
  def _setup_bindings(self):
    """
    Sets up event bindings.
    """
    self._canvas.bind('<Motion>', self._maybe_show_tooltip)
  def _rc_to_xy(self, loc):
    """
    Returns the top left corner of the connector located at row |r| column |c|.
    """
    r, c = loc
    x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING
    y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + (
        num_vertical_separators(r) * (VERTICAL_SEPARATION - CONNECTOR_SPACING))
    return x, y
  def _xy_to_rc(self, x, y):
    """
    Returns the row and column of the valid location on the proto board
        containing the point (|x|, |y|), or None if no such location exists.
    """
    for r in xrange(PROTO_BOARD_HEIGHT):
      for c in xrange(PROTO_BOARD_WIDTH):
        if valid_loc((r, c)):
          x1, y1 = self._rc_to_xy((r, c))
          x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE
          if x1 <= x <= x2 and y1 <= y <= y2:
            return r, c
    return None
  def _draw_connector(self, x, y, fill=CONNECTOR_COLOR,
      outline=CONNECTOR_COLOR):
    """
    Draws a connector at the given coordinate and with the given colors.
    """
    self._canvas.create_rectangle(x, y, x + CONNECTOR_SIZE, y + CONNECTOR_SIZE,
        fill=fill, outline=outline)
  def _draw_connectors(self):
    """
    Draws the connectors on the proto board.
    """
    for r in ROWS:
      for c in COLUMNS:
        if valid_loc((r, c)):
          self._draw_connector(*self._rc_to_xy((r, c)))
  def _draw_bus_line(self, y, color):
    """
    Draws a bus line at the given horizontal position |y| and with the given
        |color|.
    """
    x_1 = self._rc_to_xy((0, 1))[0]
    x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0]
    self._canvas.create_line(x_1, y, x_2, y, fill=color)
  def _draw_bus_lines(self):
    """
    Draws all four bus lines on the proto board.
    """
    offset = 10
    self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR)
    self._draw_bus_line(self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset,
        POSITIVE_COLOR)
    self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - (
        offset), NEGATIVE_COLOR)
    self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 1, 0))[1] + (
        CONNECTOR_SIZE + offset), POSITIVE_COLOR)
  def _draw_labels(self):
    """
    Draws the row and column labels.
    """
    # row labels
    row_labels = dict(zip(range(11, 1, -1), ['A', 'B', 'C', 'D', 'E', 'F', 'G',
        'H', 'I', 'J']))
    for r in filter(lambda r: r in row_labels, ROWS):
      self._canvas.create_text(self._rc_to_xy((r, -1)), text=row_labels[r])
      x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH))
      self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r])
    # columns labels
    h_offset = 2
    v_offset = 10
    for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS):
      x_1, y_1 = self._rc_to_xy((2, c))
      self._canvas.create_text(x_1 + h_offset, y_1 - v_offset, text=(c + 1))
      x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c))
      self._canvas.create_text(x_2 + h_offset, y_2 + CONNECTOR_SIZE + v_offset,
          text=(c + 1))
  def _draw_gnd_pwr_pins(self):
    """
    Draws pins to show the ground and power rails.
    """
    # pin positions
    g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3))
    p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3))
    # big rectangles
    large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING
    small_h_offset = 2
    large_v_offset = 7
    small_v_offset = 3
    self._canvas.create_rectangle(g_x - small_h_offset, g_y - large_v_offset,
        g_x + large_h_offset, g_y + CONNECTOR_SIZE + small_v_offset,
        fill=NEGATIVE_COLOR)
    self._canvas.create_rectangle(p_x - small_h_offset, p_y - small_v_offset,
        p_x + large_h_offset, p_y + CONNECTOR_SIZE + large_v_offset,
        fill=POSITIVE_COLOR)
    # connectors
    self._draw_connector(g_x, g_y, outline='black')
    self._draw_connector(p_x, p_y, outline='black')
    # text
    text_v_offset = 2
    text_h_offset = 17
    self._canvas.create_text(g_x + text_h_offset, g_y - text_v_offset,
        text='gnd', fill='white')
    self._canvas.create_text(p_x + text_h_offset, p_y + text_v_offset,
        text='+10', fill='white')
  def _draw_piece(self, piece):
    """
    Draws the given circuit |piece| on the canvas.
    """
    piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc))
  def _draw_pieces(self):
    """
    Draws all the pieces on the proto board.
    """
    for piece in self._proto_board.get_pieces():
      self._draw_piece(piece)
  def _draw_wire(self, wire):
    """
    Draws the given |wire| on the canvas.
    """
    x_1, y_1 = self._rc_to_xy(wire.loc_1)
    x_2, y_2 = self._rc_to_xy(wire.loc_2)
    length = int(ceil(wire.length()))
    fill = 'white'
    if 1 < length < 10:
      fill = WIRE_COLORS[length]
    elif 10 <= length < 50:
      fill = WIRE_COLORS[(length + 9) / 10]
    def draw_wire(parts=None):
      if parts:
        for part in parts:
          self._canvas.delete(part)
          del self._wire_parts[part]
      new_parts = [self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
          y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2,
          y_2 + CONNECTOR_SIZE / 2, fill=WIRE_OUTLINE, width=7,
          capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2,
          y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2,
          y_2 + CONNECTOR_SIZE / 2, fill=fill, width=3, capstyle='round')]
      for part in new_parts:
        self._canvas.tag_bind(part, '<Button-1>', lambda event: draw_wire(
            new_parts))
        self._wire_parts[part] = wire.node
    draw_wire()
  def _draw_wires(self):
    """
    Draws all the wires on the proto board.
    """
    for wire in sorted(self._proto_board.get_wires(),
        key=lambda wire: -wire.length()):
      self._draw_wire(wire)
  def _redraw_wires(self):
    """
    Erases then redraws the wires on the protoboard.
    """
    for part in self._wire_parts:
      self._canvas.delete(part)
    self._wire_parts.clear()
    self._draw_wires()
  def _draw_proto_board(self):
    """
    Draws the given |proto_board|.
    """
    self._draw_connectors()
    self._draw_bus_lines()
    self._draw_labels()
    self._draw_pieces()
    self._draw_wires()
    if self._show_pwr_gnd_pins:
      self._draw_gnd_pwr_pins()
    self._canvas.pack()
    self.pack()
  def _wire_to_cmax_str(self, wire):
    """
    Returns a CMax representation (when saved in a file) of the given |wire|.
    """
    c1, r1 = loc_to_cmax_rep(wire.loc_1)
    c2, r2 = loc_to_cmax_rep(wire.loc_2)
    return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2)
  def _to_cmax_str(self):
    """
    Returns a string CMax representation of the proto board we are visualizing.
    """
    # header
    lines = ['#CMax circuit']
    # wires
    for wire in self._proto_board.get_wires():
      lines.append(self._wire_to_cmax_str(wire))
    # circuit pieces
    for piece in self._proto_board.get_pieces():
      cmax_str = piece.to_cmax_str()
      if cmax_str:
        lines.append(cmax_str)
    # power and ground pins
    if self._show_pwr_gnd_pins:
      lines.append('+10: (61,20)')
      lines.append('gnd: (61,19)')
    return '\n'.join(lines)
  def _save_as_cmax_file(self):
    """
    Presents a dialog box that will save the proto board we are visualizing as a
        CMax file.
    """
    file_name = asksaveasfilename(title='Save as CMax file ...',
        filetypes=[('CMax files', CMAX_FILE_EXTENSION)])
    if file_name and not file_name.endswith(CMAX_FILE_EXTENSION):
      file_name += CMAX_FILE_EXTENSION
    if file_name:
      save_file = open(file_name, 'w')
      save_file.write(self._to_cmax_str())
      save_file.close()
  def _setup_menu(self):
    """
    Sets up a menu that lets the user save the proto board we are visualizing as
        a CMax file.
    """
    menu = Menu(self._parent, tearoff=0)
    save_menu = Menu(menu, tearoff=0)
    save_menu.add_command(label='Save as CMax file',
        command=self._save_as_cmax_file)
    menu.add_cascade(label='File', menu=save_menu)
    edit_menu = Menu(menu, tearoff=0)
    edit_menu.add_command(label='Redraw wires', command=self._redraw_wires)
    menu.add_cascade(label='Edit', menu=edit_menu)
    self._parent.config(menu=menu)
  def outline_piece_from_label(self, label):
    """
    Draws the appropriate outline for the circuit piece with the given |label|.
    """
    # try block in case canvas is gone with someone still calling this method
    try:
      self._canvas.delete(self._piece_outline_id)
      for piece in self._proto_board.get_pieces():
        if label in piece.label.split(','):
          self._piece_outline_id = piece.outline_label(self._canvas,
              self._rc_to_xy(piece.top_left_loc), label)
          return
    except:
      pass
  def outline_wires_from_label(self, label):
    """
    Draws outlines on the wires that have the given |label|.
    """
    # try block in case canvas is gone with someone still calling this method
    try:
      for part in self._wire_outline_ids:
        self._canvas.delete(part)
        del self._wire_parts[part]
      self._wire_outline_ids = []
      for wire in self._proto_board.get_wires():
        if wire.node == label:
          x_1, y_1 = self._rc_to_xy(wire.loc_1)
          x_2, y_2 = self._rc_to_xy(wire.loc_2)
          self._wire_outline_ids.extend([self._canvas.create_line(
              x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 +
              CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='black',
              width=7, capstyle='round'), self._canvas.create_line(x_1 +
              CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 +
              CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='cyan',
              width=3, capstyle='round')])
          for part in self._wire_outline_ids:
            self._wire_parts[part] = label
    except:
      pass
  def set_piece_highlight(self, f):
    """
    Resets the piece highlighting function to |f|.
    """
    self._piece_highlight = f
  def set_wire_highlight(self, f):
    """
    Resets the wire highlighing function to |f|.
    """
    def g(label):
      f(label)
      self.outline_wires_from_label(label)
    self._wire_highlight = g