import pango, gtk import gnoetics, tilemodel class TileView(gtk.DrawingArea, tilemodel.TileModelListener): default_font = None def __init__(self, model): gtk.DrawingArea.__init__(self) tilemodel.TileModelListener.__init__(self, model) self.__model = model self.__pixmap = None self.__layout_cache = {} self.__line_position_cache = [None] * self.__model.get_line_count() self.__tile_position_cache = {} self.__bg_color = self.get_colormap().alloc_color("lightgreen") self.__word_fg_color = self.get_colormap().alloc_color("black") self.__word_bg_color = self.get_colormap().alloc_color("white") # Color scheme for highlighted tiles self.__hi_fg_color = self.get_colormap().alloc_color("red") self.__hi_bg_color = self.get_colormap().alloc_color("white") # Color scheme for selected tiles self.__sel_fg_color = self.get_colormap().alloc_color("black") self.__sel_bg_color = self.get_colormap().alloc_color("yellow") # Color scheme for highlighted, selected tiles self.__hisel_fg_color = self.get_colormap().alloc_color("red") self.__hisel_bg_color = self.get_colormap().alloc_color("yellow") self.__x_start = 10 self.__y_start = 10 self.__x_spacing = 4 self.__tiny_x_spacing = 1 self.__y_spacing = 4 self.__stanza_spacing = 3 * self.__y_spacing self.__word_x_left_pad = 4 self.__word_x_right_pad = 3 self.__word_y_pad = 2 self.__hi_line_num = None self.__hi_col_num = None self.connect("size_request", TileView.__size_request_handler) self.connect("configure_event", TileView.__configure_handler) self.connect("expose_event", TileView.__expose_handler) self.connect("motion_notify_event", TileView.__motion_notify_handler) self.connect("enter_notify_event", TileView.__enter_notify_handler) self.connect("leave_notify_event", TileView.__leave_notify_handler) self.connect("button_press_event", TileView.__button_press_handler) self.set_events(gtk.gdk.EXPOSURE_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) def __get_layout(self, text): layout = self.__layout_cache.get(text) if layout is None: layout = pango.Layout(self.get_pango_context()) if default_font is not None: layout.set_font_description(pango.FontDescription (default_font)); layout.set_markup(text) self.__layout_cache[text] = layout return layout def __get_tile_size(self, tile): if tile == "stanza break": return 0, self.__stanza_spacing # Handle textual tiles txt = "???" is_token = (type(tile) == gnoetics.Token) if is_token: txt = tile.get_word() else: txt = tile layout = self.__get_layout(txt) w, h = layout.get_pixel_size() h += 2 * self.__word_y_pad if is_token: w += self.__word_x_left_pad + self.__word_x_right_pad return w, h def __get_line_position(self, line_num): pos = self.__line_position_cache[line_num] if pos is None: n = self.__model.get_line_length(line_num) max_h = 0 for i in range(n): tile = self.__model.get_tile(line_num, i) w, h = self.__get_tile_size(tile) max_h = max(max_h, h) if line_num == 0: y = self.__y_start else: prev_y, prev_h = self.__get_line_position(line_num-1) y = prev_y + prev_h + self.__y_spacing self.__line_position_cache[line_num] = pos = (y, max_h) return pos def __get_tile_position(self, line_num, col_num): val = self.__tile_position_cache.get((line_num, col_num)) if val is not None: return val tile = self.__model.get_tile(line_num, col_num) w, h = self.__get_tile_size(tile) if col_num == 0: y, max_h = self.__get_line_position(line_num) x = self.__x_start else: ptile = self.__model.get_tile(line_num, col_num-1) px, py, pw, ph = self.__get_tile_position(line_num, col_num-1) x = px + pw y = py if (type(ptile) == gnoetics.Token and ptile.has_right_glue()) or \ (type(tile) == gnoetics.Token and tile.has_left_glue()): x += self.__tiny_x_spacing else: x += self.__x_spacing self.__tile_position_cache[(line_num, col_num)] = (x, y, w, h) return (x, y, w, h) def __get_total_size(self): x1 = 0 y1 = 0 for i in range(self.__model.get_line_count()): j = self.__model.get_line_length(i) if j > 0: x, y, w, h = self.__get_tile_position(i, j-1) x1 = max(x1, x+w) y1 = max(y1, y+h) return x1 + self.__x_start, y1 + self.__y_start def __convert_y_to_line(self, y): if y >= self.__y_start: N = self.__model.get_line_count() for i in xrange(N): line_y, line_h = self.__get_line_position(i) if line_y <= y < line_y + line_h: return i return None def __point_to_tile(self, x, y): if x < self.__x_start: return (None, None, None) line_num = self.__convert_y_to_line(y) if line_num is None: return (None, None, None) N = self.__model.get_line_length(line_num) x0 = self.__x_start prev_right_glue = 0 for i in xrange(N): tile = self.__model.get_tile(line_num, i) w, h = self.__get_tile_size(tile) has_left_glue = 0 is_token = (type(tile) == gnoetics.Token) if is_token: has_left_glue = tile.has_left_glue() if i > 0: if not (prev_right_glue or has_left_glue): x0 += self.__x_spacing else: x0 += self.__tiny_x_spacing if x0 <= x < x0+w: return line_num, i, tile x0 += w if is_token: has_right_glue = tile.has_right_glue() return (None, None, None) def __render_tile(self, tile, x, y, hi=0, selected=0): # If this is a stanza break, do nothing if tile == "stanza break": return if hi and selected: fg = self.__hisel_fg_color bg = self.__hisel_bg_color elif hi: fg = self.__hi_fg_color bg = self.__hi_bg_color elif selected: fg = self.__sel_fg_color bg = self.__sel_bg_color else: fg = self.__word_fg_color bg = self.__word_bg_color w, h = self.__get_tile_size(tile) if w <= 0 or h <= 0: return draw_box = 1 x_pad = 0 txt = "???" if type(tile) == gnoetics.Token: txt = tile.get_word() x_pad = self.__word_x_left_pad else: draw_box = 0 bg = self.__bg_color txt = tile gc = self.get_style().bg_gc[gtk.STATE_NORMAL] old_fg = gc.foreground old_bg = gc.background gc.set_foreground(bg) self.__pixmap.draw_rectangle(gc, True, x, y, w-1, h-1) gc.set_foreground(fg) gc.set_background(bg) layout = self.__get_layout(txt) self.__pixmap.draw_layout(gc, x + x_pad, y + self.__word_y_pad, layout) if draw_box: self.__pixmap.draw_rectangle(gc, False, x, y, w-1, h-1) gc.set_foreground(old_fg) gc.set_background(old_bg) def __draw_tile(self, line_num, col_num, queue_redraw=0): tile = self.__model.get_tile(line_num, col_num) selflag = self.__model.get_selection_flag(line_num, col_num) x, y, w, h = self.__get_tile_position(line_num, col_num) hi = (line_num == self.__hi_line_num \ and col_num == self.__hi_col_num) self.__render_tile(tile, x, y, hi=hi, selected=selflag) if queue_redraw: self.queue_draw_area(x, y, w, h) def __highlight_tile(self, line_num, col_num): if self.__hi_line_num == line_num and self.__hi_col_num == col_num: return old_hi_line_num = self.__hi_line_num old_hi_col_num = self.__hi_col_num self.__hi_line_num = line_num self.__hi_col_num = col_num if old_hi_line_num is not None: self.__draw_tile(old_hi_line_num, old_hi_col_num, queue_redraw=1) if line_num is not None: self.__draw_tile(line_num, col_num, queue_redraw=1) def __draw_line(self, line_num, queue_redraw=0): n = self.__model.get_line_length(line_num) for i in range(n): self.__draw_tile(line_num, i, queue_redraw=queue_redraw) def __draw_all(self): n = self.__model.get_line_count() for i in range(n): h = self.__draw_line(i) def __size_request_handler(self, req): w, h = self.__get_total_size() req.width = w req.height = h return True def __configure_handler(self, ev): win = self.window w, h = win.get_size() self.__pixmap = gtk.gdk.Pixmap(win, w, h) # Because of a pygtk bug we can't copy the gc gc = self.get_style().bg_gc[gtk.STATE_NORMAL] fg = gc.foreground gc.set_foreground(self.__bg_color) self.__pixmap.draw_rectangle(gc, True, 0, 0, w, h) gc.set_foreground(fg) self.__draw_all() return True def __expose_handler(self, ev): x, y, w, h = ev.area gc = self.get_style().fg_gc[gtk.STATE_NORMAL] self.window.draw_drawable(gc, self.__pixmap, x, y, x, y, w, h) return False def __motion_notify_handler(self, ev): if ev.is_hint: x, y, state = ev.window.get_pointer() else: x = ev.x; y = ev.y state = ev.state line_num, col_num, tile = self.__point_to_tile(x, y) if line_num is not None and state & gtk.gdk.BUTTON1_MASK: if state & gtk.gdk.SHIFT_MASK: flag = 0 else: flag = 1 if state & gtk.gdk.CONTROL_MASK: self.__model.set_selection_flag_on_line(line_num, flag) else: self.__model.set_selection_flag(line_num, col_num, flag) self.__highlight_tile(line_num, col_num) return True def __button_press_handler(self, ev): if ev.button == 1: line_num, col_num, tile = self.__point_to_tile(ev.x, ev.y) if line_num is not None: if ev.state & gtk.gdk.SHIFT_MASK: flag = 0 else: flag = 1 if ev.state & gtk.gdk.CONTROL_MASK: self.__model.set_selection_flag_on_line(line_num, flag) else: self.__model.set_selection_flag(line_num, col_num, flag) def __enter_notify_handler(self, ev): x = ev.x; y = ev.y line_num, col_num, tile = self.__point_to_tile(x, y) if line_num is not None: self.__highlight_tile(line_num, col_num) return True def __leave_notify_handler(self, ev): self.__highlight_tile(None, None) return True def do_changed_line(self, line_num): # Reset line pos cache self.__line_position_cache = [None] * self.__model.get_line_count() self.queue_resize() def do_changed_selection(self, line_num, col_num, flag): self.__draw_tile(line_num, col_num, queue_redraw=1)