class SimulationCanvas( object ): """A canvas where blocks can be dragged around and connected up""" size = ( width, height ) = ( 550, 300 ) def __init__( self, frame ): # Create the canvas self.canvas = Canvas( frame, width=self.width, height=self.height, relief=RIDGE, background=colours["background"], borderwidth=1 ) # Add event handlers for dragable items self.canvas.tag_bind ( "DRAG", "<ButtonPress-1>", self.mouse_down ) #self.canvas.tag_bind ("DRAG", "<ButtonRelease-1>", self.mouse_release) self.canvas.tag_bind ( "DRAG", "<Enter>", self.enter ) self.canvas.tag_bind ( "DRAG", "<Leave>", self.leave ) self.canvas.pack( side=TOP ) # Some default locations self.PREVIEW_WIDTH = 80 self.PREVIEW_LOCATION = ( self.PREVIEW_X, self.PREVIEW_Y ) = ( 15, 30 ) # Draw a "preview" area self.canvas.create_line( self.PREVIEW_WIDTH, 0, self.PREVIEW_WIDTH, self.height, dash=True ) # A dict indexed by unique ID of elements in the canvas. self.blocks = {} def preview_actor( self, codefile ): """ Display a preview of an actor or compisite actor in the canvas, it will be dragable into desired position """ logging.debug( "Creating a preview of %(name)s on simulation canvas." % {'name':codefile.name} ) logging.debug( "Deleting any existing items still tagged 'preview'" ) self.canvas.delete( "preview" ) block = CanvasBlock( self.canvas, codefile, *self.PREVIEW_LOCATION ) self.blocks[block.id] = block def mouse_down( self, event ): logging.debug( "The mouse was pressed at (%d, %d)" % ( event.x, event.y ) ) logging.debug( "The mouse went down on a block. Binding mouse release..." ) selected = self.canvas.gettags( "current" ) logging.debug( "Currently selected items tags are %s" % selected.__repr__() ) self.selected_name = [tag for tag in selected if tag.startswith( "name:" ) ][0][5:] self.selected_id = [tag for tag in selected if tag.startswith( "id:" ) ][0][3:] self.selected_type = [tag for tag in selected if tag.startswith( "type:" ) ][0][5:] logging.debug( "Block selected was %s with id:%s" % ( self.selected_name, self.selected_id ) ) #self.canvas.addtag( 'Selected', 'withtag', self.selected_id ) logging.debug( "Current blocks are: %s" % self.blocks ) #self.blocks[block_id].set_colour( colours['selected'] ) if self.selected_type == "block" or self.selected_type == "text": self.blocks[self.selected_id].select(event.x, event.y) self.canvas.bind( "<ButtonRelease-1>", self.block_move_mouse_release ) elif self.selected_type.startswith("input") or self.selected_type.startswith("output"): self.blocks[self.selected_id].select_port(self.selected_type) self.canvas.bind( "<ButtonRelease-1>", self.port_connect_mouse_release ) else: logging.info("Tried to select %s" % self.selected_type) def block_move_mouse_release( self, event ): logging.debug( "The mouse was released at (%d, %d)" % ( event.x, event.y ) ) self.canvas.bind( "<ButtonRelease-1>", lambda e: None ) if event.x >= 0 and event.x <= self.canvas.winfo_width() \ and event.y >= 0 and event.y <= self.canvas.winfo_height(): logging.debug( "Valid move inside canvas. Relocating block." ) self.blocks[self.selected_id].move_to(event.x, event.y) if event.x >= self.PREVIEW_WIDTH: if self.blocks[self.selected_id].is_preview(): logging.info( "Moved out of preview zone, adding new component to model" ) #TODO HERE - add to model compiler or what ever... self.blocks[self.selected_id].unselect() else: self.blocks[self.selected_id].preview() else: logging.info( "Invalid move." ) def port_connect_mouse_release( self, event ): logging.debug( "The mouse was released at (%d, %d)" % ( event.x, event.y ) ) self.canvas.bind( "<ButtonRelease-1>", lambda e: None ) if event.x >= 0 and event.x <= self.canvas.winfo_width() \ and event.y >= 0 and event.y <= self.canvas.winfo_height(): logging.debug( "Valid location inside canvas." ) event.widget.itemconfigure( "Selected", fill="#000000" ) event.widget.itemconfigure( "type:text", fill="#000000" ) #block = self.canvas.gettags("Selected") #logging.debug("Block moved was made up of these components: %s" % block.__repr__()) self.canvas.dtag( "Selected", "Selected" ) else: logging.info( "Invalid wiring." ) def enter( self, event ): logging.debug( "Enter" ) def leave( self, event ): logging.debug( "Leaving" )
class NvimTk(object): """Wraps all nvim/tk event handling.""" def __init__(self, nvim): """Initialize with a Nvim instance.""" self._nvim = nvim self._attrs = {} self._nvim_updates = deque() self._canvas = None self._fg = '#000000' self._bg = '#ffffff' def run(self): """Start the UI.""" self._tk_setup() t = Thread(target=self._nvim_event_loop) t.daemon = True t.start() self._root.mainloop() def _tk_setup(self): self._root = Tk() self._root.bind('<<nvim_redraw>>', self._tk_nvim_redraw) self._root.bind('<<nvim_detach>>', self._tk_nvim_detach) self._root.bind('<Key>', self._tk_key) def _tk_nvim_redraw(self, *args): update = self._nvim_updates.popleft() for update in update: handler = getattr(self, '_tk_nvim_' + update[0]) for args in update[1:]: handler(*args) def _tk_nvim_detach(self, *args): self._root.destroy() def _tk_nvim_resize(self, width, height): self._tk_redraw_canvas(width, height) def _tk_nvim_clear(self): self._tk_clear_region(0, self._height - 1, 0, self._width - 1) def _tk_nvim_eol_clear(self): row, col = ( self._cursor_row, self._cursor_col, ) self._tk_clear_region(row, row, col, self._scroll_right) def _tk_nvim_cursor_goto(self, row, col): self._cursor_row = row self._cursor_col = col def _tk_nvim_cursor_on(self): pass def _tk_nvim_cursor_off(self): pass def _tk_nvim_mouse_on(self): pass def _tk_nvim_mouse_off(self): pass def _tk_nvim_insert_mode(self): pass def _tk_nvim_normal_mode(self): pass def _tk_nvim_set_scroll_region(self, top, bot, left, right): self._scroll_top = top self._scroll_bot = bot self._scroll_left = left self._scroll_right = right def _tk_nvim_scroll(self, count): top, bot = ( self._scroll_top, self._scroll_bot, ) left, right = ( self._scroll_left, self._scroll_right, ) if count > 0: destroy_top = top destroy_bot = top + count - 1 move_top = destroy_bot + 1 move_bot = bot fill_top = move_bot + 1 fill_bot = fill_top + count - 1 else: destroy_top = bot + count + 1 destroy_bot = bot move_top = top move_bot = destroy_top - 1 fill_bot = move_top - 1 fill_top = fill_bot + count + 1 # destroy items that would be moved outside the scroll region after # scrolling # self._tk_clear_region(destroy_top, destroy_bot, left, right) # self._tk_clear_region(move_top, move_bot, left, right) self._tk_destroy_region(destroy_top, destroy_bot, left, right) self._tk_tag_region('move', move_top, move_bot, left, right) self._canvas.move('move', 0, -count * self._rowsize) self._canvas.dtag('move', 'move') # self._tk_fill_region(fill_top, fill_bot, left, right) def _tk_nvim_highlight_set(self, attrs): self._attrs = attrs def _tk_nvim_put(self, data): # choose a Font instance font = self._fnormal if self._attrs.get('bold', False): font = self._fbold if self._attrs.get('italic', False): font = self._fbolditalic if font == self._fbold else self._fitalic # colors fg = "#{0:0{1}x}".format(self._attrs.get('foreground', self._fg), 6) bg = "#{0:0{1}x}".format(self._attrs.get('background', self._bg), 6) # get the "text" and "rect" which correspond to the current cell x, y = self._tk_get_coords(self._cursor_row, self._cursor_col) items = self._canvas.find_overlapping(x, y, x + 1, y + 1) if len(items) != 2: # caught part the double-width character in the cell to the left, # filter items which dont have the same horizontal coordinate as # "x" predicate = lambda item: self._canvas.coords(item)[0] == x items = filter(predicate, items) # rect has lower id than text, sort to unpack correctly rect, text = sorted(items) self._canvas.itemconfig(text, fill=fg, font=font, text=data or ' ') self._canvas.itemconfig(rect, fill=bg) self._tk_nvim_cursor_goto(self._cursor_row, self._cursor_col + 1) def _tk_nvim_bell(self): self._root.bell() def _tk_nvim_update_fg(self, fg): self._fg = "#{0:0{1}x}".format(fg, 6) def _tk_nvim_update_bg(self, bg): self._bg = "#{0:0{1}x}".format(bg, 6) def _tk_redraw_canvas(self, width, height): if self._canvas: self._canvas.destroy() self._fnormal = Font(family='Monospace', size=13) self._fbold = Font(family='Monospace', weight='bold', size=13) self._fitalic = Font(family='Monospace', slant='italic', size=13) self._fbolditalic = Font(family='Monospace', weight='bold', slant='italic', size=13) self._colsize = self._fnormal.measure('A') self._rowsize = self._fnormal.metrics('linespace') self._canvas = Canvas(self._root, width=self._colsize * width, height=self._rowsize * height) self._tk_fill_region(0, height - 1, 0, width - 1) self._cursor_row = 0 self._cursor_col = 0 self._scroll_top = 0 self._scroll_bot = height - 1 self._scroll_left = 0 self._scroll_right = width - 1 self._width, self._height = ( width, height, ) self._canvas.pack() def _tk_fill_region(self, top, bot, left, right): # create columns from right to left so the left columns have a # higher z-index than the right columns. This is required to # properly display characters that cross cell boundary for rownum in range(bot, top - 1, -1): for colnum in range(right, left - 1, -1): x1 = colnum * self._colsize y1 = rownum * self._rowsize x2 = (colnum + 1) * self._colsize y2 = (rownum + 1) * self._rowsize # for each cell, create two items: The rectangle is used for # filling background and the text is for cell contents. self._canvas.create_rectangle(x1, y1, x2, y2, fill=self._bg, width=0) self._canvas.create_text(x1, y1, anchor='nw', font=self._fnormal, width=1, fill=self._fg, text=' ') def _tk_clear_region(self, top, bot, left, right): self._tk_tag_region('clear', top, bot, left, right) self._canvas.itemconfig('clear', fill=self._bg) self._canvas.dtag('clear', 'clear') def _tk_destroy_region(self, top, bot, left, right): self._tk_tag_region('destroy', top, bot, left, right) self._canvas.delete('destroy') self._canvas.dtag('destroy', 'destroy') def _tk_tag_region(self, tag, top, bot, left, right): x1, y1 = self._tk_get_coords(top, left) x2, y2 = self._tk_get_coords(bot, right) self._canvas.addtag_overlapping(tag, x1, y1, x2 + 1, y2 + 1) def _tk_get_coords(self, row, col): x = col * self._colsize y = row * self._rowsize return x, y def _tk_key(self, event): if 0xffe1 <= event.keysym_num <= 0xffee: # this is a modifier key, ignore. Source: # https://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm return # Translate to Nvim representation of keys send = [] if event.state & 0x1: send.append('S') if event.state & 0x4: send.append('C') if event.state & (0x8 | 0x80): send.append('A') special = len(send) > 0 key = event.char if _is_invalid_key(key): special = True key = event.keysym send.append(SPECIAL_KEYS.get(key, key)) send = '-'.join(send) if special: send = '<' + send + '>' nvim = self._nvim nvim.session.threadsafe_call(lambda: nvim.input(send)) def _nvim_event_loop(self): self._nvim.session.run(self._nvim_request, self._nvim_notification, lambda: self._nvim.attach_ui(80, 24)) self._root.event_generate('<<nvim_detach>>', when='tail') def _nvim_request(self, method, args): raise Exception('This UI does not implement any methods') def _nvim_notification(self, method, args): if method == 'redraw': self._nvim_updates.append(args) self._root.event_generate('<<nvim_redraw>>', when='tail')
class SimulationCanvas(object): """A canvas where blocks can be dragged around and connected up""" size = (width, height) = (550, 300) def __init__(self, frame): # Create the canvas self.canvas = Canvas(frame, width=self.width, height=self.height, relief=RIDGE, background=colours["background"], borderwidth=1) # Add event handlers for dragable items self.canvas.tag_bind("DRAG", "<ButtonPress-1>", self.mouse_down) #self.canvas.tag_bind ("DRAG", "<ButtonRelease-1>", self.mouse_release) self.canvas.tag_bind("DRAG", "<Enter>", self.enter) self.canvas.tag_bind("DRAG", "<Leave>", self.leave) self.canvas.pack(side=TOP) # Some default locations self.PREVIEW_WIDTH = 80 self.PREVIEW_LOCATION = (self.PREVIEW_X, self.PREVIEW_Y) = (15, 30) # Draw a "preview" area self.canvas.create_line(self.PREVIEW_WIDTH, 0, self.PREVIEW_WIDTH, self.height, dash=True) # A dict indexed by unique ID of elements in the canvas. self.blocks = {} def preview_actor(self, codefile): """ Display a preview of an actor or compisite actor in the canvas, it will be dragable into desired position """ logging.debug("Creating a preview of %(name)s on simulation canvas." % {'name': codefile.name}) logging.debug("Deleting any existing items still tagged 'preview'") self.canvas.delete("preview") block = CanvasBlock(self.canvas, codefile, *self.PREVIEW_LOCATION) self.blocks[block.id] = block def mouse_down(self, event): logging.debug("The mouse was pressed at (%d, %d)" % (event.x, event.y)) logging.debug( "The mouse went down on a block. Binding mouse release...") selected = self.canvas.gettags("current") logging.debug("Currently selected items tags are %s" % selected.__repr__()) self.selected_name = [ tag for tag in selected if tag.startswith("name:") ][0][5:] self.selected_id = [tag for tag in selected if tag.startswith("id:")][0][3:] self.selected_type = [ tag for tag in selected if tag.startswith("type:") ][0][5:] logging.debug("Block selected was %s with id:%s" % (self.selected_name, self.selected_id)) #self.canvas.addtag( 'Selected', 'withtag', self.selected_id ) logging.debug("Current blocks are: %s" % self.blocks) #self.blocks[block_id].set_colour( colours['selected'] ) if self.selected_type == "block" or self.selected_type == "text": self.blocks[self.selected_id].select(event.x, event.y) self.canvas.bind("<ButtonRelease-1>", self.block_move_mouse_release) elif self.selected_type.startswith( "input") or self.selected_type.startswith("output"): self.blocks[self.selected_id].select_port(self.selected_type) self.canvas.bind("<ButtonRelease-1>", self.port_connect_mouse_release) else: logging.info("Tried to select %s" % self.selected_type) def block_move_mouse_release(self, event): logging.debug("The mouse was released at (%d, %d)" % (event.x, event.y)) self.canvas.bind("<ButtonRelease-1>", lambda e: None) if event.x >= 0 and event.x <= self.canvas.winfo_width() \ and event.y >= 0 and event.y <= self.canvas.winfo_height(): logging.debug("Valid move inside canvas. Relocating block.") self.blocks[self.selected_id].move_to(event.x, event.y) if event.x >= self.PREVIEW_WIDTH: if self.blocks[self.selected_id].is_preview(): logging.info( "Moved out of preview zone, adding new component to model" ) #TODO HERE - add to model compiler or what ever... self.blocks[self.selected_id].unselect() else: self.blocks[self.selected_id].preview() else: logging.info("Invalid move.") def port_connect_mouse_release(self, event): logging.debug("The mouse was released at (%d, %d)" % (event.x, event.y)) self.canvas.bind("<ButtonRelease-1>", lambda e: None) if event.x >= 0 and event.x <= self.canvas.winfo_width() \ and event.y >= 0 and event.y <= self.canvas.winfo_height(): logging.debug("Valid location inside canvas.") event.widget.itemconfigure("Selected", fill="#000000") event.widget.itemconfigure("type:text", fill="#000000") #block = self.canvas.gettags("Selected") #logging.debug("Block moved was made up of these components: %s" % block.__repr__()) self.canvas.dtag("Selected", "Selected") else: logging.info("Invalid wiring.") def enter(self, event): logging.debug("Enter") def leave(self, event): logging.debug("Leaving")
class NvimTk(object): """Wraps all nvim/tk event handling.""" def __init__(self, nvim): """Initialize with a Nvim instance.""" self._nvim = nvim self._attrs = {} self._nvim_updates = deque() self._canvas = None self._fg = '#000000' self._bg = '#ffffff' def run(self): """Start the UI.""" self._tk_setup() t = Thread(target=self._nvim_event_loop) t.daemon = True t.start() self._root.mainloop() def _tk_setup(self): self._root = Tk() self._root.bind('<<nvim_redraw>>', self._tk_nvim_redraw) self._root.bind('<<nvim_detach>>', self._tk_nvim_detach) self._root.bind('<Key>', self._tk_key) def _tk_nvim_redraw(self, *args): update = self._nvim_updates.popleft() for update in update: handler = getattr(self, '_tk_nvim_' + update[0]) for args in update[1:]: handler(*args) def _tk_nvim_detach(self, *args): self._root.destroy() def _tk_nvim_resize(self, width, height): self._tk_redraw_canvas(width, height) def _tk_nvim_clear(self): self._tk_clear_region(0, self._height - 1, 0, self._width - 1) def _tk_nvim_eol_clear(self): row, col = (self._cursor_row, self._cursor_col,) self._tk_clear_region(row, row, col, self._scroll_right) def _tk_nvim_cursor_goto(self, row, col): self._cursor_row = row self._cursor_col = col def _tk_nvim_cursor_on(self): pass def _tk_nvim_cursor_off(self): pass def _tk_nvim_mouse_on(self): pass def _tk_nvim_mouse_off(self): pass def _tk_nvim_insert_mode(self): pass def _tk_nvim_normal_mode(self): pass def _tk_nvim_set_scroll_region(self, top, bot, left, right): self._scroll_top = top self._scroll_bot = bot self._scroll_left = left self._scroll_right = right def _tk_nvim_scroll(self, count): top, bot = (self._scroll_top, self._scroll_bot,) left, right = (self._scroll_left, self._scroll_right,) if count > 0: destroy_top = top destroy_bot = top + count - 1 move_top = destroy_bot + 1 move_bot = bot fill_top = move_bot + 1 fill_bot = fill_top + count - 1 else: destroy_top = bot + count + 1 destroy_bot = bot move_top = top move_bot = destroy_top - 1 fill_bot = move_top - 1 fill_top = fill_bot + count + 1 # destroy items that would be moved outside the scroll region after # scrolling # self._tk_clear_region(destroy_top, destroy_bot, left, right) # self._tk_clear_region(move_top, move_bot, left, right) self._tk_destroy_region(destroy_top, destroy_bot, left, right) self._tk_tag_region('move', move_top, move_bot, left, right) self._canvas.move('move', 0, -count * self._rowsize) self._canvas.dtag('move', 'move') # self._tk_fill_region(fill_top, fill_bot, left, right) def _tk_nvim_highlight_set(self, attrs): self._attrs = attrs def _tk_nvim_put(self, data): # choose a Font instance font = self._fnormal if self._attrs.get('bold', False): font = self._fbold if self._attrs.get('italic', False): font = self._fbolditalic if font == self._fbold else self._fitalic # colors fg = "#{0:0{1}x}".format(self._attrs.get('foreground', self._fg), 6) bg = "#{0:0{1}x}".format(self._attrs.get('background', self._bg), 6) # get the "text" and "rect" which correspond to the current cell x, y = self._tk_get_coords(self._cursor_row, self._cursor_col) items = self._canvas.find_overlapping(x, y, x + 1, y + 1) if len(items) != 2: # caught part the double-width character in the cell to the left, # filter items which dont have the same horizontal coordinate as # "x" predicate = lambda item: self._canvas.coords(item)[0] == x items = filter(predicate, items) # rect has lower id than text, sort to unpack correctly rect, text = sorted(items) self._canvas.itemconfig(text, fill=fg, font=font, text=data or ' ') self._canvas.itemconfig(rect, fill=bg) self._tk_nvim_cursor_goto(self._cursor_row, self._cursor_col + 1) def _tk_nvim_bell(self): self._root.bell() def _tk_nvim_update_fg(self, fg): self._fg = "#{0:0{1}x}".format(fg, 6) def _tk_nvim_update_bg(self, bg): self._bg = "#{0:0{1}x}".format(bg, 6) def _tk_redraw_canvas(self, width, height): if self._canvas: self._canvas.destroy() self._fnormal = Font(family='Monospace', size=13) self._fbold = Font(family='Monospace', weight='bold', size=13) self._fitalic = Font(family='Monospace', slant='italic', size=13) self._fbolditalic = Font(family='Monospace', weight='bold', slant='italic', size=13) self._colsize = self._fnormal.measure('A') self._rowsize = self._fnormal.metrics('linespace') self._canvas = Canvas(self._root, width=self._colsize * width, height=self._rowsize * height) self._tk_fill_region(0, height - 1, 0, width - 1) self._cursor_row = 0 self._cursor_col = 0 self._scroll_top = 0 self._scroll_bot = height - 1 self._scroll_left = 0 self._scroll_right = width - 1 self._width, self._height = (width, height,) self._canvas.pack() def _tk_fill_region(self, top, bot, left, right): # create columns from right to left so the left columns have a # higher z-index than the right columns. This is required to # properly display characters that cross cell boundary for rownum in range(bot, top - 1, -1): for colnum in range(right, left - 1, -1): x1 = colnum * self._colsize y1 = rownum * self._rowsize x2 = (colnum + 1) * self._colsize y2 = (rownum + 1) * self._rowsize # for each cell, create two items: The rectangle is used for # filling background and the text is for cell contents. self._canvas.create_rectangle(x1, y1, x2, y2, fill=self._background, width=0) self._canvas.create_text(x1, y1, anchor='nw', font=self._fnormal, width=1, fill=self._foreground, text=' ') def _tk_clear_region(self, top, bot, left, right): self._tk_tag_region('clear', top, bot, left, right) self._canvas.itemconfig('clear', fill=self._bg) self._canvas.dtag('clear', 'clear') def _tk_destroy_region(self, top, bot, left, right): self._tk_tag_region('destroy', top, bot, left, right) self._canvas.delete('destroy') self._canvas.dtag('destroy', 'destroy') def _tk_tag_region(self, tag, top, bot, left, right): x1, y1 = self._tk_get_coords(top, left) x2, y2 = self._tk_get_coords(bot, right) self._canvas.addtag_overlapping(tag, x1, y1, x2 + 1, y2 + 1) def _tk_get_coords(self, row, col): x = col * self._colsize y = row * self._rowsize return x, y def _tk_key(self, event): if 0xffe1 <= event.keysym_num <= 0xffee: # this is a modifier key, ignore. Source: # https://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm return # Translate to Nvim representation of keys send = [] if event.state & 0x1: send.append('S') if event.state & 0x4: send.append('C') if event.state & (0x8 | 0x80): send.append('A') special = len(send) > 0 key = event.char if _is_invalid_key(key): special = True key = event.keysym send.append(SPECIAL_KEYS.get(key, key)) send = '-'.join(send) if special: send = '<' + send + '>' nvim = self._nvim nvim.session.threadsafe_call(lambda: nvim.input(send)) def _nvim_event_loop(self): self._nvim.session.run(self._nvim_request, self._nvim_notification, lambda: self._nvim.attach_ui(80, 24)) self._root.event_generate('<<nvim_detach>>', when='tail') def _nvim_request(self, method, args): raise Exception('This UI does not implement any methods') def _nvim_notification(self, method, args): if method == 'redraw': self._nvim_updates.append(args) self._root.event_generate('<<nvim_redraw>>', when='tail')