def on_load_ascii_from_file(self, filename): try: file = open(filename, 'r') str = file.readlines() startpos = Pos(0, 0) pos = Pos(0, 0) for line in str: # create a TEXT instance for each line # fill selected_objects... symbol = Text(pos, line) selection = SelectedObjects(startpos=startpos, symbol=symbol) pos += Pos(0, 1) self.selected_objects.append(selection) file.close() pub.sendMessage('OBJECTS_SELECTED', objects=self.selected_objects) return True except IOError as e: msg = _( "Unable to open file for reading: {} error({}): {}").format( filename, e.errno, e.strerror) pub.sendMessage('STATUS_MESSAGE', msg=msg, type=WARNING) return False except UnicodeDecodeError as e: msg = _( "Unable to open file for reading: {} error({}): {}").format( filename, e.encoding, e.reason) pub.sendMessage('STATUS_MESSAGE', msg=msg, type=WARNING) return False
def __init__(self, id=0, grid=None, ori=None, mirrored=None, startpos=None, endpos=None): self._id = id self._has_pickpoint = True if ori is None: self._ori = 0 else: self._ori = ori if mirrored is None: self._mirrored = 0 else: self._mirrored = mirrored if grid is None: self._grid = self.default else: self._grid = grid if startpos is None: self._startpos = Pos(0, 0) else: self._startpos = startpos if endpos is None: self._endpos = Pos(0, 0) else: self._endpos = endpos self._is_symbol = True self._is_text = False self._is_line = False
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()
def _line_match(self, idx, ori, pos): """ Match a character in the grid against a Magic Line pattern. :param idx: number of the line matching pattern to be used :param ori: orientation of the line to be drawn :param pos: character position (col, row) coordinates :return result: True if a match was found, otherwise False :return ori: magic line orientation :return char: terminal character """ lmd = MagicLineSettings.LMD[idx] result = True m_ori = None m_char = None if pos > Pos(0, 0) and (ori is None or ori == lmd.ori): for j, row in enumerate(lmd.pattern): for i, char in enumerate(row): if char != 'x': m_pos = pos + Pos(i - 1, j - 1) m_cell = self.cell(m_pos) if m_cell != char: result = False break if result: m_ori = lmd.ori m_char = lmd.char # print("line_match, idx:", idx, " m_ori:", m_ori, " m_char:", m_char) else: result = False return result, m_ori, m_char
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 _representation(self): self._repr = dict() pos = self._startpos incr = Pos(1, 0) for row in range(self._size[1]): pos.x = self._startpos.x for col in range(self._size[0]): self._repr[pos] = CELL_ERASE pos += incr pos += Pos(0, 1)
def _corner_line(self, ori): startpos = self._startpos endpos = self._endpos dx = endpos.x - startpos.x dy = endpos.y - startpos.y if (dy >= 0) ^ (ori != VERTICAL): corner_char = Preferences.values['LOWER_CORNER'] else: corner_char = Preferences.values['UPPER_CORNER'] top = 0 left = 0 if ori == HORIZONTAL: top = startpos.y left = endpos.x elif ori == VERTICAL: top = endpos.y left = startpos.x # horizontal line if dx > 0: startv = startpos.x + 1 # don't overwrite the terminal char (in the startposition) endv = endpos.x else: startv = endpos.x + 1 # don't overwrite the terminal char (in the startposition) endv = startpos.x if abs(dx) > 1: for temp in range(endv - startv): pos = Pos(startv + temp, top) if self.cell(pos) == Preferences.values['LINE_VERT']: char = Preferences.values['CROSSING'] else: char = Preferences.values['LINE_HOR'] self._repr[pos] = char # vertical line if dy > 0: startv = startpos.y + 1 # don't overwrite the terminal char (in the startposition) endv = endpos.y else: startv = endpos.y + 1 # don't overwrite the terminal char (in the startposition) endv = startpos.y if abs(dy) > 1: for temp in range(endv - startv): pos = Pos(left, startv + temp) if self.cell(pos) == Preferences.values['LINE_HOR']: char = Preferences.values['CROSSING'] else: char = Preferences.values['LINE_VERT'] self._repr[pos] = char # corner character, if there is a corner if (ori == HORIZONTAL and abs(dy) > 0) or (ori == VERTICAL and abs(dx) > 0): self._repr[Pos(left, top)] = corner_char
def _representation(self): self._repr = dict() pos = self._startpos incr = Pos(1, 0) for row in self.grid: pos.x = self._startpos.x for char in row: if char != ' ': self._repr[pos] = char pos += incr pos += Pos(0, 1)
def test_rect(self): c = Controller() c.on_new() start = Pos(5, 5) end = Pos(15, 15) c.on_paste_rect(start, end) filename = 'tmp/test_rect.aac' self.assertTrue(c.on_write_to_file(filename))
def test_comparison(self): a = Pos(10, 15) b = Pos(12, 21) c = Pos(12, 21) self.assertTrue(a < b) self.assertFalse(a > b) self.assertTrue(a <= b) self.assertFalse(a >= b) self.assertTrue(b == c) self.assertFalse(a == b)
def test_erase(self): c = Controller() c.on_new() # NB test should be run against the default (en_US) component library c.on_component_changed('AND gate') c.on_paste_objects(Pos(4, 2)) c.on_eraser_selected((5, 5)) c.on_paste_objects(Pos(5, 2)) filename = 'tmp/test_edit_erase.aac' self.assertTrue(c.on_write_to_file(filename))
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 _representation(self): ul = self._startpos ur = Pos(self._endpos.x, self._startpos.y) bl = Pos(self._startpos.x, self._endpos.y) br = self._endpos line1 = Line(ul, ur, Line.LINE1) line2 = Line(ur, br, Line.LINE4) line3 = Line(bl, br, Line.LINE1) line4 = Line(ul, bl, Line.LINE4) self._repr = dict() self._repr.update(line1.repr) self._repr.update(line3.repr) self._repr.update(line2.repr) self._repr.update(line4.repr)
def pointer_dir_avg(self): """Return the pointer direction in relation to the previous position.""" (x, y) = self._drag_currentpos.xy length = len(self._drag_prevpos) assert length > 0 x_sum = 0 y_sum = 0 for pos in self._drag_prevpos: x_sum += pos.x y_sum += pos.y x_avg = x_sum / length y_avg = y_sum / length dx = abs(x - x_avg) dy = abs(y - y_avg) if dx > dy: dir = HORIZONTAL else: dir = VERTICAL # previous position keeps a list of the last n pointer locations self._drag_prevpos.append(Pos(x, y)) if len(self._drag_prevpos) > 5: self._drag_prevpos.pop(0) return dir
def rotate(self): w = self._endpos.x - self._startpos.x h = self._endpos.y - self._startpos.y ul = self._startpos br = Pos(self._startpos.x + h, self._startpos.y + w) self._startpos = ul self._endpos = br
def selecting_state(self, pos, event): if self._selection.item == ROW: row = pos.grid_cr().y pub.sendMessage('GRID_ROW', row=row, action=self._selection.action) elif self._selection.item == COL: col = pos.grid_cr().x pub.sendMessage('GRID_COL', col=col, action=self._selection.action) elif self._selection.item in (TEXT, TEXT_BLOCK): button = event.button if button == 1: # left button self._selection.state = SELECTED self._symbol.startpos = pos.grid_cr() pub.sendMessage('PASTE_TEXT', symbol=self._symbol) elif button == 3: # right button self._symbol.rotate() # FIXME more elegant options? otoh grid_view() is owner of the Text Symbol pub.sendMessage('ORIENTATION_CHANGED', ori=self._symbol.ori_as_str) elif self._selection.item == OBJECT: self._selection.state = SELECTED # select the object within the cursor rect ul = pos br = ul + Pos(Preferences.values['GRIDSIZE_W'], Preferences.values['GRIDSIZE_H']) self._drag_startpos = ul self._drag_endpos = br self._selection.startpos = ul self._selection.endpos = br self._selection.maxpos = self.max_pos_grid pub.sendMessage('SELECTION_CHANGED', selected=True)
def on_paste_grid(self): """ Copy the content of the clipboard to the grid. ASCII lines, terminated by CR, are interpreted as rows. """ selected = [] pos = Pos(0, 0) relative_pos = Pos(0, 0) content = xerox.paste().splitlines() for line in content: symbol = Text(relative_pos, line) selection = SelectedObjects(startpos=pos, symbol=symbol) selected.append(selection) relative_pos += Pos(0, 1) self.selected_objects = selected pub.sendMessage('OBJECTS_SELECTED', objects=self.selected_objects)
def on_hover(self, widget, event): if not self.has_focus(): self.grab_focus() width = Preferences.values['GRIDSIZE_W'] height = Preferences.values['GRIDSIZE_H'] self._hover_pos = self.calc_position(event.x, event.y) delta = self._hover_previous_pos - self._hover_pos if abs(delta.x) > width / 2 or abs(delta.y) > height / 2: moved_enough = True self._hover_previous_pos = self._hover_pos else: # reduce message flooding and superfluous drawing updates moved_enough = False if moved_enough: pub.sendMessage('POINTER_MOVED', pos=self._hover_pos.grid_cr()) if self._selection.state == SELECTING and \ self._selection.item == OBJECT: if moved_enough: pub.sendMessage('SELECTOR_MOVED', pos=self._hover_pos.grid_cr()) if not Preferences.values['SELECTION_DRAG'] \ and self._selection.state == SELECTING \ and self._selection.item in (DRAW_RECT, ARROW, RECT, ERASER, LINE, MAG_LINE, DIR_LINE): offset = Pos(event.x, event.y) - self._drag_startpos if moved_enough: self.on_drag_update(None, offset.x, offset.y)
def _representation(self): x, y = (self._endpos - self._startpos).xy # TODO better representation of straight line (using ASCII chars)? if abs(x) > 0: angle = atan(y / x) else: angle = pi / 2 if angle < radians(-75): linechar = '|' if angle >= radians(-75) and angle <= radians(-52): linechar = '.' elif angle > radians(-52) and angle <= radians(-37): linechar = '/' elif angle > radians(-37) and angle <= radians(-15): linechar = '.' elif angle > radians(-15) and angle <= radians(15): linechar = '-' elif angle > radians(15) and angle <= radians(37): linechar = '.' elif angle > radians(37) and angle <= radians(52): linechar = '\\' elif angle > radians(52) and angle <= radians(75): linechar = '.' elif angle > radians(75): linechar = '|' else: linechar = '?' repr = dict() line = bresenham(self._startpos.x, self._startpos.y, self._endpos.x, self._endpos.y) for coord in line: pos = Pos(coord[0], coord[1]) repr[pos] = linechar self._repr = repr
def endpos_capped(self): """Return end position capped by the coordinates maximum.""" x, y = self._endpos.xy if self._endpos.x > self._maxpos.x: x = self._maxpos.x if self._endpos.y > self._maxpos.y: y = self._maxpos.y return Pos(x, y)
def test_duplicate(self): c = Controller() c.on_new() # NB test should be run against the default (en_US) component library c.on_component_changed('AND gate') c.on_paste_objects(Pos(4, 2)) c.on_component_changed('NAND gate') c.on_paste_objects(Pos(4, 2)) rect = (Pos(4, 2), Pos(5, 3)) c.on_cut(rect) filename = 'tmp/test_edit_duplicate.aac' self.assertTrue(c.on_write_to_file(filename))
def _repr_hor(self): """ Horizontal arrow representation: d E \ b----c\ | /e a----g/ / S f S: start position E: end position a-g: arrow vertices """ startpos = self._startpos endpos = self._endpos h = startpos.y - endpos.y if h == 0: h = 3 h = h - h % 3 h2 = h / 2 h3 = h / 3 my = (startpos.y + endpos.y) / 2 a = Pos(startpos.x, startpos.y - h3) b = Pos(startpos.x, endpos.y + h3) c = Pos(endpos.x - h2, endpos.y + h3) d = Pos(endpos.x - h2, endpos.y) e = Pos(endpos.x, my) f = Pos(endpos.x - h2, startpos.y) g = Pos(endpos.x - h2, startpos.y - h3) self._repr_poly(a, b, c, d, e, f, g) self._pickpoint = c
def _repr_vert(self): """ Vertical arrow representation: e \\ E / \ / \ d/-c g-\f | | | | S b-a S: start position E: end position a-g: arrow vertices """ startpos = self._startpos endpos = self._endpos w = endpos.x - startpos.x if w == 0: w = 3 w = w - w % 3 w2 = w / 2 w3 = w / 3 mx = (startpos.x + endpos.x) / 2 a = Pos(endpos.x - w3, startpos.y) b = Pos(startpos.x + w3, startpos.y) c = Pos(startpos.x + w3, endpos.y + w2) d = Pos(startpos.x, endpos.y + w2) e = Pos(mx, endpos.y) f = Pos(endpos.x, endpos.y + w2) g = Pos(endpos.x - w3, endpos.y + w2) self._repr_poly(a, b, c, d, e, f, g) self._pickpoint = c
def test_lines(self): c = Controller() c.on_new() start = Pos(5, 5) end = Pos(15, 5) line_types = (Line.MLINE, Line.LINE1, Line.LINE2, Line.LINE3, Line.LINE4) for type in line_types: c.on_paste_line(start, end, type) start += Pos(0, 5) end += Pos(0, 5) filename = 'tmp/test_lines.aac' self.assertTrue(c.on_write_to_file(filename))
def _representation(self): """Compose the line elements.""" self._direction() self._repr = dict() terminal = self._terminal start_terminal = terminal start = self._startpos end = self._endpos pos = start if self._dir == HORIZONTAL: line_char = Preferences.values['LINE_HOR'] incr = Pos(1, 0) if start > end: # line drawn from right-to-left pos = Pos(end.x, start.y) end = start elif self._dir == VERTICAL: if self._type == self.LINE4: start_terminal = Preferences.values['TERMINAL4_VERT'] line_char = Preferences.values['LINE_VERT'] incr = Pos(0, 1) if start > end: # line drawn from right-to-left pos = Pos(start.x, end.y) end = start else: line_char = "?" incr = Pos(1, 1) # startpoint terminal if start_terminal is None: self._repr[pos] = line_char else: self._repr[pos] = start_terminal pos += incr while pos < end: self._repr[pos] = line_char pos += incr # endpoint terminal if self._terminal is None: self._repr[pos] = line_char else: self._repr[pos] = self._terminal
def test_in_rect(self): a = Pos(10, 15) b = Pos(12, 21) ul = Pos(9, 14) br = Pos(12, 20) r = (ul, br) self.assertTrue(a.in_rect(r)) self.assertFalse(b.in_rect(r))
def _representation(self): self._repr = dict() startpos = self._startpos pos = self._startpos str = self._text.split('\n') if self._ori == 0 or self._ori == 2: for line in str: pos.x = startpos.x for char in line: if char != ' ': self._repr[pos] = char pos += Pos(1, 0) pos += Pos(0, 1) elif self._ori == 1 or self._ori == 3: for line in str: pos.y = startpos.y for char in line: if char != ' ': self._repr[pos] = char pos += Pos(0, 1) pos += Pos(1, 0)
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 test_read_ascii(self): c = Controller() c.on_new() filename = 'tests/files/test_ascii.txt' self.assertTrue(c.on_load_ascii_from_file(filename)) pos = Pos(0, 0) c.on_paste_objects(pos) filename = 'tmp/test_ascii.aac' self.assertTrue(c.on_write_to_file(filename))
def draw(self, ctx, pos=None): """ :param ctx: the Cairo context :param pos: target position in grid canvas (x,y) coordinates """ ctx.save() ctx.set_source_rgb(0.75, 0.75, 0.75) x_start, y_start = pos.xy # size from grid (col,row) to view (x,y) coordinates size = Pos(self._size[0], self._size[1]).view_xy() width = size.x height = size.y ctx.rectangle(x_start, y_start, width, height) ctx.fill() ctx.restore()