def test_conversion(self): a = Pos(10, 15) b = Pos(12, 21) tmp = a.grid_cr() self.assertEquals(tmp.x, 1) self.assertEquals(tmp.y, 0) tmp = a.view_xy() self.assertEquals(tmp.x, 100) self.assertEquals(tmp.y, 240) a.snap_to_grid() self.assertEquals(a.x, 10) self.assertEquals(a.y, 0) b = Pos(10, 16) tmp = b.grid_cr() self.assertEquals(tmp.x, 1) self.assertEquals(tmp.y, 1) tmp = b.view_xy() self.assertEquals(tmp.x, 100) self.assertEquals(tmp.y, 256) b.snap_to_grid() self.assertEquals(b.x, 10) self.assertEquals(b.y, 16) c = Pos(11, 17) tmp = c.grid_cr() self.assertEquals(tmp.x, 1) self.assertEquals(tmp.y, 1) tmp = c.view_xy() self.assertEquals(tmp.x, 110) self.assertEquals(tmp.y, 272) c.snap_to_grid() self.assertEquals(c.x, 10) self.assertEquals(c.y, 16)
def calc_position(self, x, y): """Calculate the grid view position.""" pos = Pos(x, y) pos.snap_to_grid() return pos
class MatrixView(Gtk.DrawingArea): def __init__(self, lmd): super(MatrixView, self).__init__() self._surface = None self._hover_pos = Pos(0, 0) self.set_can_focus(True) self.set_focus_on_click(True) self.connect('draw', self.on_draw) self.connect('configure-event', self.on_configure) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self.connect('button-press-event', self.on_button_press) # https://stackoverflow.com/questions/44098084/how-do-i-handle-keyboard-events-in-gtk3 self.add_events(Gdk.EventMask.KEY_PRESS_MASK) self.connect('key-press-event', self.on_key_press) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion-notify-event', self.on_hover) self._cursor_on = True self._hover_pos = Pos(0, 0) self.init_line_matching_data(lmd) # https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-add-tick-callback self.start_time = time.time() self.cursor_callback = self.add_tick_callback(self.toggle_cursor) pub.subscribe(self.on_matching_data_changed, 'MATCHING_DATA_CHANGED') def init_surface(self, area): """Initialize Cairo surface.""" if self._surface is not None: # destroy previous buffer self._surface.finish() self._surface = None # create a new buffer self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, area.get_allocated_width(), area.get_allocated_height()) def init_line_matching_data(self, lmd): self.on_matching_data_changed(lmd) def calc_offset(self): """Calculate the upper left coordinate where the matrix will be drawn.""" grid_w = Preferences.values['GRIDSIZE_W'] grid_h = Preferences.values['GRIDSIZE_H'] x_offset = round((self._surface.get_width() - 3 * grid_w) / 2) y_offset = round((self._surface.get_height() - 3 * grid_h) / 2) self._offset = Pos(x_offset, y_offset) self._offset.snap_to_grid() def on_configure(self, area, event, data=None): self.init_surface(self) self.calc_offset() context = cairo.Context(self._surface) self.do_drawing(context) self._surface.flush() return False def on_matching_data_changed(self, lmd): self._matrix = lmd.pattern self._start_char = lmd.char self._start_ori = lmd.ori def on_button_press(self, button, event): return True def on_key_press(self, widget, event): # TODO Will this work in other locale too? def filter_non_printable(ascii): char = '' if (ascii > 31 and ascii < 255) or ascii == 9: char = chr(ascii) return char def valid_index(pos): if pos.x >= 0 and pos.x < 3 and pos.y >= 0 and pos.y < 3: return True else: return False def next_char(): # move to the next character or the next line if grid_pos.x < 2: self._hover_pos += Pos(1, 0).view_xy() elif grid_pos.y < 2: self._hover_pos += Pos(-2, 1).view_xy() def previous_char(): # move to the previous character or the previous line if grid_pos.x > 0: if grid_pos.x <= 2: self._hover_pos -= Pos(1, 0).view_xy() elif grid_pos.y > 0: self._hover_pos += Pos(2, -1).view_xy() grid_pos = self._hover_pos - self._offset grid_pos.snap_to_grid() grid_pos = grid_pos.grid_cr() value = event.keyval if value in (Gdk.KEY_Shift_L, Gdk.KEY_Shift_R): pass elif value == Gdk.KEY_Left or value == Gdk.KEY_BackSpace: previous_char() elif value == Gdk.KEY_Right: next_char() elif value == Gdk.KEY_Up: self._hover_pos -= Pos(0, 1).view_xy() elif value == Gdk.KEY_Down: self._hover_pos += Pos(0, 1).view_xy() elif value & 255 != 13: # enter if valid_index(grid_pos): str = filter_non_printable(value) self._matrix[grid_pos.y][grid_pos.x] = str next_char() return True def on_hover(self, widget, event): if not self.has_focus(): self.grab_focus() self._hover_pos = Pos(event.x, event.y) self._hover_pos.snap_to_grid() self.queue_resize() def on_draw(self, area, ctx): if self._surface is not None: ctx.set_source_surface(self._surface, 0.0, 0.0) ctx.paint() else: print(_("Invalid surface")) return False def do_drawing(self, ctx): self.draw_gridlines(ctx) self.draw_content(ctx) self.draw_cursor(ctx) def draw_gridlines(self, ctx): grid_w = Preferences.values['GRIDSIZE_W'] grid_h = Preferences.values['GRIDSIZE_H'] offset = self._offset # draw a background ctx.set_source_rgb(0.95, 0.95, 0.85) ctx.set_line_width(0.5) ctx.set_tolerance(0.1) ctx.set_line_join(cairo.LINE_JOIN_ROUND) ctx.new_path() ctx.rectangle(offset.x, offset.y, 3 * grid_w, 3 * grid_h) ctx.fill() # draw the gridlines # TODO use CSS for uniform colors? ctx.set_source_rgb(0.75, 0.75, 0.75) ctx.set_line_width(0.5) ctx.set_tolerance(0.1) ctx.set_line_join(cairo.LINE_JOIN_ROUND) x_max = offset.x + 3 * grid_w y_max = offset.y + 3 * grid_h # horizontal lines y = offset.y for count in range(4): ctx.new_path() ctx.move_to(offset.x, y) ctx.line_to(x_max, y) ctx.stroke() y += grid_h # vertical lines x = offset.x for count in range(4): ctx.new_path() ctx.move_to(x, offset.y) ctx.line_to(x, y_max) ctx.stroke() x += grid_w def draw_content(self, ctx): if self._matrix is None: return grid_w = Preferences.values['GRIDSIZE_W'] grid_h = Preferences.values['GRIDSIZE_H'] offset = self._offset ctx.set_source_rgb(0.1, 0.1, 0.1) use_pango_font = Preferences.values['PANGO_FONT'] if use_pango_font: # https://sites.google.com/site/randomcodecollections/home/python-gtk-3-pango-cairo-example # https://developer.gnome.org/pango/stable/pango-Cairo-Rendering.html layout = PangoCairo.create_layout(ctx) desc = Pango.font_description_from_string(Preferences.values['FONT']) layout.set_font_description(desc) else: ctx.set_font_size(Preferences.values['FONTSIZE']) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) y = offset.y for r in self._matrix: x = offset.x for c in r: if use_pango_font: ctx.move_to(x, y) layout.set_text(str(c), -1) PangoCairo.show_layout(ctx, layout) else: # the Cairo text glyph origin is its left-bottom corner ctx.move_to(x, y + Preferences.values['FONTSIZE']) ctx.show_text(str(c)) x += grid_w y += grid_h def draw_cursor(self, ctx): if not self.has_focus(): return ctx.save() ctx.set_line_width(1.5) ctx.set_line_join(cairo.LINE_JOIN_ROUND) if self._cursor_on: ctx.set_source_rgb(0.75, 0.75, 0.75) else: ctx.set_source_rgb(0.5, 0.5, 0.5) x = self._hover_pos.x y = self._hover_pos.y ctx.rectangle(x, y, Preferences.values['GRIDSIZE_W'], Preferences.values['GRIDSIZE_H']) ctx.stroke() ctx.restore() def toggle_cursor(self, widget, frame_clock, user_data=None): now = time.time() elapsed = now - self.start_time if elapsed > 0.5: self.start_time = now self._cursor_on = not self._cursor_on self.queue_resize() return GLib.SOURCE_CONTINUE