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)
class GridView(Gtk.DrawingArea): def __init__(self): super(GridView, self).__init__() self.surface = None self._grid = None self._objects = None self._hover_pos = Pos(0, 0) self._hover_previous_pos = Pos(0, 0) self._selection = Selection(None) # selection position self._drag_dir = None self._drag_startpos = None self._drag_endpos = None self._drag_currentpos = None self._drag_prevpos = [] # text self._cursor_on = True self._text = "" # pickpoints self._show_symbol_pickpoints = True self._show_line_pickpoints = True self._show_text_pickpoints = True self.set_can_focus(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) if Preferences.values['SELECTION_DRAG']: self._gesture_drag = Gtk.GestureDrag.new(self) self._gesture_drag.connect('drag-begin', self.on_drag_begin) self._gesture_drag.connect('drag-end', self.on_drag_end) self._gesture_drag.connect('drag-update', self.on_drag_update) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion-notify-event', self.on_hover) # 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) # self.remove_tick_callback(self.cursor_callback) # subscriptions pub.subscribe(self.set_grid, 'NEW_GRID') pub.subscribe(self.on_add_text, 'ADD_TEXT') pub.subscribe(self.on_add_textblock, 'ADD_TEXTBLOCK') pub.subscribe(self.on_character_selected, 'CHARACTER_SELECTED') pub.subscribe(self.on_symbol_selected, 'SYMBOL_SELECTED') pub.subscribe(self.on_objects_selected, 'OBJECTS_SELECTED') pub.subscribe(self.on_selecting_eraser, 'SELECTING_ERASER') pub.subscribe(self.on_selecting_rect, 'SELECTING_RECT') pub.subscribe(self.on_selecting_object, 'SELECTING_OBJECT') pub.subscribe(self.on_selecting_row, 'SELECTING_ROW') pub.subscribe(self.on_selecting_col, 'SELECTING_COL') pub.subscribe(self.on_nothing_selected, 'NOTHING_SELECTED') pub.subscribe(self.on_draw_mag_line, 'DRAW_MAG_LINE') pub.subscribe(self.on_draw_dir_line, 'DRAW_LINE0') pub.subscribe(self.on_draw_line, 'DRAW_LINE1') pub.subscribe(self.on_draw_line, 'DRAW_LINE2') pub.subscribe(self.on_draw_line, 'DRAW_LINE3') pub.subscribe(self.on_draw_line, 'DRAW_LINE4') pub.subscribe(self.on_draw_rect, 'DRAW_RECT') pub.subscribe(self.on_draw_arrow, 'DRAW_ARROW') # printing pub.subscribe(self.on_begin_print, 'BEGIN_PRINT') pub.subscribe(self.on_draw_page, 'DRAW_PAGE') pub.subscribe(self.on_draw_pdf, 'DRAW_PDF') # pickpoints pub.subscribe(self.on_show_symbol_pickpoints, 'SHOW_SYMBOL_PICKPOINTS') pub.subscribe(self.on_show_line_pickpoints, 'SHOW_LINE_PICKPOINTS') pub.subscribe(self.on_show_text_pickpoints, 'SHOW_TEXT_PICKPOINTS') def set_grid(self, grid): self._grid = grid self.set_viewport_size() def set_viewport_size(self): # https://stackoverflow.com/questions/11546395/how-to-put-gtk-drawingarea-into-gtk-layout width = self._grid.nr_cols * Preferences.values['GRIDSIZE_W'] height = self._grid.nr_rows * Preferences.values['GRIDSIZE_H'] self.set_size_request(width, height) @property def max_pos(self): x_max = self.surface.get_width() y_max = self.surface.get_height() return Pos(x_max, y_max) @property def max_pos_grid(self): x_max = self._grid.nr_cols * Preferences.values['GRIDSIZE_W'] y_max = self._grid.nr_rows * Preferences.values['GRIDSIZE_H'] return Pos(x_max, y_max) @property def drag_rect(self): """Return the selected rectangle (upper-left and bottom-right) position.""" if self._drag_startpos <= self._drag_endpos: ul = self._drag_startpos br = self._drag_endpos else: ul = self._drag_endpos br = self._drag_startpos ul = ul.grid_cr() br = br.grid_cr() return ul, br 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 on_configure(self, area, event, data=None): self.init_surface(self) context = cairo.Context(self.surface) self.do_drawing(context) self.surface.flush() return False 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 # (don't) show pickpoints def on_show_symbol_pickpoints(self, state): self._show_symbol_pickpoints = state def on_show_line_pickpoints(self, state): self._show_line_pickpoints = state def on_show_text_pickpoints(self, state): self._show_text_pickpoints = state # printing def on_begin_print(self, parms): operation, print_ctx = parms operation.set_n_pages(1) def on_draw_page(self, parms): operation, print_ctx, page_num = parms # w = print_ctx.get_width() # h = print_ctx.get_height() # print("width: {} height:{})".format(w, h)) ctx = print_ctx.get_cairo_context() # self.draw_border(ctx, w, h) ctx.scale(0.5, 0.5) self.draw_content(ctx) def on_draw_pdf(self, filename): # don't use the drawing_area, so that this method can be run from (nose) test method (w/o GUI) # w = self.get_allocated_width() # h = self.get_allocated_height() # FIXME Set Portrait or Landscape dimensions based upon prefs or printer settings w, h = (560, 784) surface = cairo.PDFSurface(filename, w, h) ctx = cairo.Context(surface) # self.draw_border(ctx, w, h) ctx.scale(0.5, 0.5) self.draw_content(ctx) surface.finish() msg = _("PDF Exported to {}").format(filename) pub.sendMessage('STATUS_MESSAGE', msg=msg) # SELECTIONs def on_nothing_selected(self): self._selection = Selection(None) self.queue_resize() def on_add_text(self): self._selection = Selection(item=TEXT, state=SELECTING) self._text = "" self._symbol = Text(Pos(0, 0), self._text) self.grab_focus() def on_add_textblock(self, text): self._selection = Selection(item=TEXT_BLOCK, state=SELECTING) self._symbol = Text(Pos(0, 0), text) self._text = text def on_character_selected(self, char): self._selection = Selection(item=CHARACTER, state=SELECTED) self._symbol = char def on_symbol_selected(self, symbol): # only components (can be rotated or mirrored) self._selection = Selection(item=COMPONENT, state=SELECTED) self._symbol = symbol def on_objects_selected(self, objects): # one object or multiple objects have been selected self._selection = Selection(item=OBJECTS) self._selection.state = SELECTED self._objects = objects def on_selecting_object(self, objects): # select a single object self._selection = SelectionObject() self._objects = objects def on_selecting_eraser(self): self._selection = SelectionEraser() def on_selecting_rect(self, objects): self._selection = SelectionRect() self._objects = objects def on_selecting_arrow(self, objects): self._selection = SelectionArrow() self._objects = objects def on_selecting_row(self, action): self._selection = SelectionRow(action) pub.sendMessage('STATUS_MESSAGE', msg='') def on_selecting_col(self, action): self._selection = SelectionCol(action) pub.sendMessage('STATUS_MESSAGE', msg='') # LINES def on_draw_mag_line(self): self._selection = Selection(item=MAG_LINE) self._symbol = MagLine(Pos(0, 0), Pos(1, 1), self._grid.cell) def on_draw_dir_line(self, type): self._selection = Selection(item=DIR_LINE) self._symbol = DirLine(Pos(0, 0), Pos(1, 1)) pub.sendMessage('STATUS_MESSAGE', msg='') def on_draw_line(self, type): self._selection = Selection(item=LINE) self._symbol = Line(Pos(0, 0), Pos(1, 1), type=type) pub.sendMessage('STATUS_MESSAGE', msg='') def on_draw_rect(self): self._selection = Selection(item=DRAW_RECT) self._symbol = Rect(Pos(0, 0), Pos(1, 1)) pub.sendMessage('STATUS_MESSAGE', msg='') def on_draw_arrow(self): self._selection = Selection(item=ARROW) self._symbol = Arrow(Pos(0, 0), Pos(1, 1)) pub.sendMessage('STATUS_MESSAGE', msg='') # TEXT ENTRY 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 # modifier = event.state # name = Gdk.keyval_name(event.keyval) value = event.keyval if value == Gdk.KEY_Escape: # exit drawing if self._selection.state == SELECTING and \ self._selection.item in (DRAW_RECT, ARROW, LINE, MAG_LINE, DIR_LINE): self._selection.state = IDLE return True # exit selection mode elif self._selection.item == OBJECT: self._selection.state = SELECTING return True elif self._selection.item in (RECT, ERASER): self._selection.state = IDLE return True if value & 255 == ord('r') and \ self._selection.item in (CHARACTER, COMPONENT, OBJECTS, TEXT_BLOCK): pub.sendMessage('ROTATE_SYMBOL') return True if value & 255 == ord('m') and \ self._selection.item in (COMPONENT, OBJECTS, TEXT_BLOCK): pub.sendMessage('MIRROR_SYMBOL') return True # check the event modifiers (can also use CONTROL_MASK, etc) # shift = (event.state & Gdk.ModifierType.SHIFT_MASK) if value == Gdk.KEY_Left or value == Gdk.KEY_BackSpace: if len(self._text) > 0: self._text = self._text[:-1] elif value & 255 == 13: # enter self._text += '\n' else: str = filter_non_printable(value) self._text += str return True # DRAWING def do_drawing(self, ctx): self.draw_background(ctx) self.draw_gridlines(ctx) self.draw_content(ctx) self.draw_selection(ctx) def draw_border(self, ctx, w, h): """draw a border at 1% of the page-size.""" ctx.save() ctx.set_source_rgb(0.75, 0.75, 0.75) ctx.set_line_width(0.25) ctx.rectangle(w * 0.01, h * 0.01, w * 0.99, h * 0.99) # noqa E226 ctx.stroke() ctx.restore() def draw_background(self, ctx): """Draw a background with the size of the grid.""" 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) x_max, y_max = self.max_pos_grid.xy ctx.new_path() ctx.rectangle(0, 0, x_max, y_max) ctx.fill() def draw_gridlines(self, ctx): # 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, y_max = self.max_pos.xy x_incr = Preferences.values['GRIDSIZE_W'] y_incr = Preferences.values['GRIDSIZE_H'] # horizontal lines y = Preferences.values['GRIDSIZE_H'] while y <= y_max: ctx.new_path() ctx.move_to(0, y) ctx.line_to(x_max, y) ctx.stroke() y += y_incr # vertical lines x = 0 while x <= x_max: ctx.new_path() ctx.move_to(x, 0) ctx.line_to(x, y_max) ctx.stroke() x += x_incr def draw_content(self, ctx): if self._grid is None: return 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 = 0 for r in self._grid.grid: x = 0 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 += Preferences.values['GRIDSIZE_W'] y += Preferences.values['GRIDSIZE_H'] # no reference to surface dimension, to allow to be run from (nose) test (w/o GUI) # if y >= self.surface.get_height(): # break def draw_selection(self, ctx): ctx.save() if self._selection.state == IDLE: if self._selection.item == RECT: self.mark_all_objects(ctx) elif self._selection.state == SELECTING: self.draw_selecting_state(ctx) elif self._selection.state == SELECTED: self.draw_selected_state(ctx) ctx.restore() def draw_selected_state(self, ctx): if self._selection.item in (CHARACTER, COMPONENT): self._symbol.draw(ctx, self._hover_pos) elif self._selection.item in (OBJECT, RECT): # draw the selection rectangle self._selection.startpos = self._drag_startpos self._selection.endpos = self._drag_endpos self._selection.maxpos = self.max_pos_grid self._selection.draw(ctx) self.mark_all_objects(ctx) elif self._selection.item == OBJECTS: self.mark_all_objects(ctx) self.draw_selected_objects(ctx) def draw_selecting_state(self, ctx): if self._selection.item == OBJECT: self.mark_all_objects(ctx) self.draw_cursor(ctx) elif self._selection.item in (TEXT, TEXT_BLOCK): self.draw_cursor(ctx) self._symbol.startpos = self._hover_pos.grid_cr() self._symbol.text = self._text self._symbol.draw(ctx) else: ctx.set_source_rgb(0.5, 0.5, 0.75) ctx.set_line_join(cairo.LINE_JOIN_ROUND) if self._selection.item in (ROW, COL): self._selection.startpos = self._hover_pos else: self._selection.startpos = self._drag_startpos self._selection.endpos = self._drag_currentpos self._selection.maxpos = self.max_pos_grid if self._selection.item == RECT: self.mark_all_objects(ctx) self._selection.draw(ctx) elif self._selection.item in (MAG_LINE, LINE, DIR_LINE, DRAW_RECT, ARROW): self._symbol.startpos = self._selection.startpos.grid_cr() self._symbol.endpos = self._selection.endpos.grid_cr() self._symbol.draw(ctx) elif self._selection.item: # draw it, if we have any valid (not None) selection self._selection.draw(ctx) def draw_cursor(self, ctx): 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, y = self._hover_pos.xy # TODO meelopen met de tekst (blijft nu aan 't begin staan) 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() elif elapsed > 0.25: self.queue_resize() return GLib.SOURCE_CONTINUE def mark_all_objects(self, ctx): """Mark all objects on the grid canvas.""" ctx.save() for ref in self._objects: if ref.symbol.has_pickpoint: if (self._show_symbol_pickpoints and ref.symbol.is_symbol) or \ (self._show_line_pickpoints and ref.symbol.is_line) or \ (self._show_text_pickpoints and ref.symbol.is_text): ctx.set_source_rgb(1, 0, 0) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) # FIXME the pickpoint of a mostleft position (x=0) will not show as it falls of the grid pos = ref.symbol.pickpoint_pos.view_xy() # the text glyph origin is its left-bottom corner y_xbase = pos.y + Preferences.values['FONTSIZE'] ctx.move_to(pos.x, y_xbase) ctx.show_text(MARK_CHAR) # mark the upper-left corner ctx.restore() def draw_selected_objects(self, ctx): """Draw multiple objects selection.""" ctx.save() for ref in self._objects: ctx.set_source_rgb(1, 0, 0) ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) # offset between the current position and the ul position of the original selection rectangle offset = self._hover_pos - ref.startpos.view_xy() pos = ref.symbol.startpos.view_xy() + offset ref.symbol.draw(ctx, pos) ctx.restore() def on_button_press(self, widget, event): pos = self.calc_position(event.x, event.y) pub.sendMessage('POINTER_MOVED', pos=pos.grid_cr()) if not Preferences.values['SELECTION_DRAG'] \ and self._selection.item in (DRAW_RECT, ARROW, ERASER, RECT, LINE, MAG_LINE, DIR_LINE): if self._selection.state == IDLE: self.on_drag_begin(None, event.x, event.y) elif self._selection.state == SELECTING: offset = Pos(event.x, event.y) - self._drag_startpos self.on_drag_end(None, offset.x, offset.y) elif self._selection.state == SELECTING: self.selecting_state(pos, event) elif self._selection.state == SELECTED: self.selected_state(event) widget.queue_resize() def selected_state(self, event): pos = self._hover_pos pos = pos.grid_cr() if self._selection.item in (CHARACTER, COMPONENT, OBJECTS): # https://stackoverflow.com/questions/6616270/right-click-menu-context-menu-using-pygtk button = event.button if button == 1: # left button pub.sendMessage('PASTE_OBJECTS', pos=pos) elif button == 3: # right button pub.sendMessage('ROTATE_SYMBOL') 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 calc_position(self, x, y): """Calculate the grid view position.""" pos = Pos(x, y) pos.snap_to_grid() return pos def on_drag_begin(self, widget, x_start, y_start): if self._selection.state == IDLE and self._selection.item in ( DRAW_RECT, ARROW, RECT, ERASER, LINE, MAG_LINE, DIR_LINE): pass else: return pos = self.calc_position(x_start, y_start) self._drag_dir = None self._drag_startpos = pos self._drag_currentpos = pos self._drag_prevpos = [] self._drag_prevpos.append(pos) self._selection.state = SELECTING def on_drag_end(self, widget, x_offset, y_offset): if self._selection.state == SELECTING and self._selection.item in ( DRAW_RECT, ARROW, RECT, ERASER, LINE, MAG_LINE, DIR_LINE): pass else: return offset = self.calc_position(x_offset, y_offset) self._drag_endpos = self._drag_startpos + offset # position to grid (col, row) coordinates startpos = self._drag_startpos.grid_cr() endpos = self._drag_endpos.grid_cr() self._selection.state = SELECTED if self._selection.item == DRAW_RECT: pub.sendMessage('PASTE_RECT', startpos=startpos, endpos=endpos) elif self._selection.item == ARROW: pub.sendMessage('PASTE_ARROW', startpos=startpos, endpos=endpos) elif self._selection.item == RECT: pub.sendMessage('SELECTION_CHANGED', selected=True) elif self._selection.item == ERASER: size = offset.grid_cr().xy pub.sendMessage('ERASE', startpos=startpos, size=size) elif self._selection.item == LINE: if self._drag_dir == HORIZONTAL: endpos.y = startpos.y elif self._drag_dir == VERTICAL: endpos.x = startpos.x pub.sendMessage("PASTE_LINE", startpos=startpos, endpos=endpos, type=self._symbol.type) elif self._selection.item == MAG_LINE: pub.sendMessage("PASTE_MAG_LINE", startpos=startpos, endpos=endpos) elif self._selection.item == DIR_LINE: pub.sendMessage("PASTE_DIR_LINE", startpos=startpos, endpos=endpos) def on_drag_update(self, widget, x_offset, y_offset): if self._selection.state == SELECTING and self._selection.item in ( DRAW_RECT, ARROW, RECT, ERASER, LINE, MAG_LINE, DIR_LINE): pass else: return offset = self.calc_position(x_offset, y_offset) pos = self._drag_startpos + offset pos.snap_to_grid() if self._selection.item in (DRAW_RECT, ARROW, RECT, ERASER, DIR_LINE, MAG_LINE): self._drag_currentpos = pos elif self._selection.item == LINE: self._drag_currentpos = pos # snap to either a horizontal or a vertical straight line self._drag_dir = self.pointer_dir() def pointer_dir(self): """Return the pointer direction in relation to the start position.""" dx = abs(self._drag_currentpos.x - self._drag_startpos.x) dy = abs(self._drag_currentpos.y - self._drag_startpos.y) if dx > dy: dir = HORIZONTAL else: dir = VERTICAL return dir 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 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)