class KeyboardView(Frame): INPUT_KEYS = 'q2w3er5t6y7ui9o0pzsxdcfvbhnjm' def __init__(self, master, instrument=None): Frame.__init__(self, master) self.instrument = instrument self.canvas = Canvas(self, width=KEYBOARD_WIDTH, height=KEYBOARD_HEIGHT, background='gray', highlightthickness=0 ) self.canvas.pack() self.keyboard_keys = {} for tone in range(MIN_TONE, MAX_TONE): self.keyboard_keys[tone] = self.create_key(tone) self.running = True self._worker() def _worker(self): if not self.running: return for k in self.keyboard_keys.values(): k.work() self.after(UPDATE_INTERVAL, self._worker) def set_instrument(self, new_instrument): # print "set_instrument: %s" % str(new_instrument) for key in self.keyboard_keys.values(): key.release() old_instrument = self.instrument self.instrument = new_instrument if not old_instrument is None: old_instrument.off() def create_key(self, tone): x0 = tone * KEY_WIDTH y0 = 0 key = Key(tone, self, self.canvas, x0, y0) self.canvas.tag_bind(key.widget, "<Button-1>", key.press) self.canvas.tag_bind(key.widget, "<ButtonRelease-1>", key.release) if 0 <= tone < len(self.INPUT_KEYS): input_key = self.INPUT_KEYS[tone] self.bind_all("<Key-%s>" % input_key, key.press) self.bind_all("<KeyRelease-%s>" % input_key, key.release) return key def destroy(self): self.running = False return Frame.destroy(self)
class Palette(Frame): """ Tkinter Frame that displays all items that can be added to the board. """ def __init__(self, parent, board, width=PALETTE_WIDTH, height=PALETTE_HEIGHT): """ |board|: the board on to which items will be added from this palette. |width|: the width of this palette. |height|: the height of this palette. """ assert isinstance(board, Board), 'board must be a Board' Frame.__init__(self, parent, background=PALETTE_BACKGROUND_COLOR) self.board = board # canvas on which items are displayed self.canvas = Canvas(self, width=width, height=height, highlightthickness=0, background=PALETTE_BACKGROUND_COLOR) self.width = width self.height = height # x-position of last item added on the LEFT side of this palette self.current_left_x = PALETTE_PADDING # x-position of last item added on the RIGHT side of this palette self.current_right_x = self.width # line to separate left and right sides of palette self.left_right_separation_line_id = None # setup ui self.canvas.pack() self.pack() def _add_item_callback(self, drawable_type, desired_offset_x, **kwargs): """ Returns a callback method that, when called, adds an item of the given |drawable_type| to the board, at the given |desired_offset_x|. """ assert issubclass(drawable_type, Drawable), ('drawable must be a Drawable ' 'subclass') def callback(event): # clear current message on the board, if any self.board.remove_message() # create new drawable new_drawable = drawable_type(**kwargs) desired_offset_y = (self.board.height - new_drawable.height - PALETTE_PADDING) desired_offset = (desired_offset_x, desired_offset_y) self.board.add_drawable(new_drawable, desired_offset) return new_drawable return callback def _spawn_types_callback(self, types_to_add, desired_offset_x): """ Returns a callback method that, when called, adds the items given in |types_to_add| to the board, starting at the given |desired_offset_x|. """ assert isinstance(types_to_add, list), 'types_to_add must be a list' def callback(event): dx = 0 # assign the same color and group_id to the types being spawned color = '#%02X%02X%02X' % (randint(0, 200), randint(0, 200), randint(0, 200)) group_id = int(round(time() * 1000)) num_drawables_added = 0 for add_type, add_kwargs in types_to_add: add_kwargs['color'] = color add_kwargs['group_id'] = group_id new_drawable = self._add_item_callback(add_type, desired_offset_x + dx, **add_kwargs)(event) if new_drawable: num_drawables_added += 1 dx += new_drawable.width + BOARD_GRID_SEPARATION if num_drawables_added > 1: self.board._action_history.combine_last_n(num_drawables_added) return callback def add_drawable_type(self, drawable_type, side, callback, types_to_add=None, **kwargs): """ Adds a drawable type for display on this palette. |drawable_type|: a subclass of Drawable to display. |side|: the side of this palette on which to put the display (LEFT or RIGHT). |callback|: method to call when display item is clicked. If None, the default callback adds an item of the display type to the board. |types_to_add|: a list of Drawables to add to the board when this item is clicked on the palette, or None if such a callback is not desired. |**kwargs|: extra arguments needed to initialize the drawable type. """ assert issubclass(drawable_type, Drawable), ('drawable must be a Drawable ' 'subclass') # create a sample (display) drawable display = drawable_type(**kwargs) # draw the display on the appropriate side of the palette if side == RIGHT: offset_x = self.current_right_x - PALETTE_PADDING - display.width self.current_right_x -= display.width + PALETTE_PADDING # update left-right separation line if self.left_right_separation_line_id: self.canvas.delete(self.left_right_separation_line_id) separation_x = self.current_right_x - PALETTE_PADDING self.left_right_separation_line_id = self.canvas.create_line( separation_x, 0, separation_x, self.height, fill=PALETTE_SEPARATION_LINE_COLOR) else: # if given |side| is illegal, assume LEFT offset_x = self.current_left_x + PALETTE_PADDING self.current_left_x += PALETTE_PADDING + display.width + PALETTE_PADDING offset_y = (self.height - display.height) / 2 if offset_y % BOARD_GRID_SEPARATION: offset_y = (offset_y / BOARD_GRID_SEPARATION) * BOARD_GRID_SEPARATION offset = (offset_x, offset_y) display.draw_on(self.canvas, offset) display.draw_connectors(self.canvas, offset) # attach callback to drawn parts # default callback adds items of this drawable type to the board if callback is None: callback = self._add_item_callback(drawable_type, offset_x, **kwargs) if ( types_to_add is None) else self._spawn_types_callback(types_to_add, offset_x) else: assert types_to_add is None, ('if callback is provided, types_to_add ' 'will not be used') # bind callback for part in display.parts: self.canvas.tag_bind(part, '<Button-1>', callback) self.canvas.tag_bind(part, '<Double-Button-1>', callback) for connector in display.connectors: self.canvas.tag_bind(connector.canvas_id, '<Button-1>', callback) self.canvas.tag_bind(connector.canvas_id, '<Double-Button-1>', callback) return display def draw_separator(self): """ Draws a separation line at the current left position. """ self.canvas.create_line(self.current_left_x, 0, self.current_left_x, self.height, fill=PALETTE_SEPARATION_LINE_COLOR) self.current_left_x += PALETTE_PADDING
class at_graph(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.u = utils('atoutput.pkl') self.km = dict() self.price = dict() self.km[0] = (min(self.u.all_km), max(self.u.all_km)) self.price[0] = (min(self.u.all_price), max(self.u.all_price)) self.zoom_level = 0 try: self.parent.title("Auto trader results") self.is_standalone = True except: self.is_standalone = False self.style = Style() self.style.theme_use("classic") # Assume the parent is the root widget; make the frame take up the # entire widget size. print self.is_standalone if self.is_standalone: self.w, self.h = map(int, self.parent.geometry().split('+')[0].split('x')) self.w, self.h = 800, 800 else: self.w, self.h = 600, 600 self.c = None # Are they hovering over a data point? self.is_hovering = False # Filter the description strings: lower and whiten any non-matching # data point. self.filter = '' self.re = list() self.replot() def replot(self, zlfrac=None): """Replot the graph. If zlfrac is not None, then it should be a fractional value between 0 and 1; this is used to do smooth zooming, which doesn't plot the axes (it only redraws the car points).""" if self.c is not None: self.c.destroy() self.c = Canvas(self, height=self.h, width=self.w, bd=1, bg='#f3f5f9') self.c.grid(sticky=S, pady=1, padx=1) zl = self.zoom_level if zlfrac is not None: z1l, z1h = self.zoom_price_start z2l, z2h = self.zoom_price_end price_low = z1l + (z2l - z1l) * zlfrac price_high = z1h + (z2h - z1h) * zlfrac z1l, z1h = self.zoom_km_start z2l, z2h = self.zoom_km_end km_low = z1l + (z2l - z1l) * zlfrac km_high = z1h + (z2h - z1h) * zlfrac self.axis((price_low, price_high), 'y', draw=False) self.axis((km_low, km_high), 'x', draw=False) self.car_points(draw=False) else: self.axis(self.price[zl], 'y') self.axis(self.km[zl], 'x') self.car_points() self.pack(fill=BOTH, expand=1) def xyp(self, x, y): "Given x in km and y in $, return canvas position (xp, yp)." xp = int(math.floor((1.0 * x - self.x1) / (self.x2 - self.x1) \ * (self.xp2 - self.xp1) + self.xp1 + 0.5)) yp = int(math.floor((1.0 * y - self.y1) / (self.y2 - self.y1) \ * (self.yp2 - self.yp1) + self.yp1 + 0.5)) return (xp, yp) def axis(self, arange, ax, draw=True): "Add an axis ax='x' or 'y', with arange=(min, max) values." if draw: a1, a2, ast = self.u.axis(*arange) else: a1, a2 = arange ast = (a2 - a1) * 0.2 nt = int(math.floor((a2 - a1) / ast + 0.5)) + 1 st_offset = 50 # Remember the min and max axis values, along with the canvas points # that correspond to each location (xp1 and xp2). This allows us to # calculate where on the canvas a particular (x, y) value falls. if ax == 'x': self.x1, self.x2 = a1, a2 self.xp1, self.xp2 = st_offset, self.w - st_offset self.xtick = [a1 + i * ast for i in range(nt)] # Remember where the midpoint of the axis is, relative to canvas. self.xmid = (self.xp1 + self.xp2) / 2 else: self.y1, self.y2 = a1, a2 self.yp1, self.yp2 = self.h - st_offset, st_offset self.ytick = [a1 + i * ast for i in range(nt)] # Remember where the midpoint of the axis is, relative to canvas. self.ymid = (self.yp1 + self.yp2) / 2 # Figure out tick labels. atick = ['%g' % ((a1 + i * ast) / 1000) for i in range(nt)] # Figure out maximum decimal places on all tick labels, and ensure # they all have that many decimal places. max_dec = max(map(lambda x: 0 if '.' not in x else len(x.split('.')[1]), atick)) if max_dec > 0: atick = map(lambda x: x + '.' + '0'*max_dec if '.' not in x else x + '0'*(max_dec - len(x.split('.')[1])), atick) yst, xst = self.h - st_offset, st_offset # Draw axis line proper, and axis label. if draw: if ax == 'x': self.c.create_line(xst, yst, self.w - st_offset, yst) xp = (xst + self.w - st_offset) / 2 self.c.create_text(xp, yst + 30, text='Mileage (km x 1000)') else: self.c.create_line(xst, yst, xst, st_offset) self.c.create_text(xst, st_offset - 30, text='Price') self.c.create_text(xst, st_offset - 15, text='($000)') tick_anchor = [N, E][ax == 'y'] tick_x, tick_y = xst, yst tick_step = ([self.w, self.h][ax == 'y'] - st_offset * 2 * 1.0) / \ (nt - 1) label_offset = 3 for i1, tick in enumerate(atick): x_of, y_of = -label_offset, label_offset if ax == 'y': y_of = int(-i1 * tick_step) else: x_of = int(i1 * tick_step) if draw: self.c.create_text(tick_x + x_of, tick_y + y_of, text=tick, anchor=tick_anchor) x_mini, y_mini = 0, 0 x_maxi, y_maxi = 0, 0 if ax == 'y': x_of += label_offset x_mini, x_maxi = 8, self.w - st_offset * 2 # Remember what y coord this grid line is at. if i1 == 0: self.y_grid = [] self.y_grid.append(tick_y + y_of) else: y_of -= label_offset y_mini, y_maxi = -8, st_offset * 2 - self.h # Remember what x coord this grid line is at. if i1 == 0: self.x_grid = [] self.x_grid.append(tick_x + x_of) if draw: # Draw the little solid tick, next to the axis. self.c.create_line(tick_x + x_of, tick_y + y_of, tick_x + x_of + x_mini, tick_y + y_of + y_mini) # Draw a dashed grid line, across the entire graph. self.c.create_line(tick_x + x_of, tick_y + y_of, tick_x + x_of + x_maxi, tick_y + y_of + y_maxi, dash=(1, 3)) def car_points(self, draw=True): "Plot the cars themselves." # 199 215 151 151 199 224 230 162 157 250 224 167 178 165 192 249 200 216 204 204 204 191 173 158 color_order = ['#c7d797', '#97c7e0', '#e6a29d', '#fae0a7', '#b2a5c0', '#f9c8d8', '#bfad9e', '#cccccc'] #color_order = ['#98df8a', '#dbdb8d', '#aec7e8', '#c9acd4', '#f7b6d2', # '#ffbb80', '#dc9b8d', '#e9ab17', '#dddddd'] # Those colors above aren't saturated enough. Saturate them more. color_order = map(lambda x: resaturate(x, -80), color_order) # Change color depending on year. cy = dict() for i1, year in enumerate(reversed(sorted(set(self.u.all_year)))): cy[year] = color_order[-1] if i1 < len(color_order): cy[year] = color_order[i1] i1 = -1 # Tuples of (index into self.u.all_* arrays, x position, y position). self.ov_dict = dict() if draw: self.c.focus_set() self.c.bind('<Button-1>', func=self.zoom) self.c.bind('<Button-2>', func=self.unzoom) self.c.bind('<Left>', func=self.left_key) self.c.bind('<Right>', func=self.right_key) self.c.bind('<Up>', func=self.up_key) self.c.bind('<Down>', func=self.down_key) legend = set() osz = 3 + self.zoom_level * 1 # Total vehicle count, and vehicles which pass the filter count. self.vcount = self.fcount = 0 for year, km, price in zip(self.u.all_year, self.u.all_km, self.u.all_price): x, y = self.xyp(km, price) i1 += 1 if x < self.x_grid[0] or x > self.x_grid[-1] or \ y > self.y_grid[0] or y < self.y_grid[-1]: continue self.vcount += 1 legend.add((year, cy[year])) filtered = False if not re.search(self.filter, self.u.all_descr[i1], re.I): filtered = True # If a data point is filtered out, make its outline reflect its # model year, and fill it with white. # # Else, make its outline and fill color reflect the model year, and # upon mouseover, make it entirely red. ov = self.c.create_oval(x-osz, y-osz, x+osz, y+osz, outline=cy[year], activeoutline=['red', cy[year]][filtered], fill=[cy[year], 'white'][filtered], activefill=['red', 'white'][filtered], ) self.ov_dict[ov] = (i1, x, y, cy[year], filtered) # If a data point is filtered out, mousing over it does nothing, # and also, lower it behind everything else. if filtered: self.c.lower(ov) else: self.fcount += 1 if draw: use_tag = 'Tag %d' % i1 self.c.addtag_withtag(use_tag, ov) self.c.tag_bind(use_tag, sequence='<Enter>', func=self.mouseover) self.c.tag_bind(use_tag, sequence='<Leave>', func=self.mouseoff) self.c.tag_bind(use_tag, sequence='<Button-1>', func=self.select) if draw: # OK, add a legend for every year that's displayed. i1 = 0 for yr, color in reversed(sorted(legend)): xp, yp = self.x_grid[-1] + 10, self.y_grid[-1] + 15 * i1 self.c.create_oval(xp-osz, yp-osz, xp+osz, yp+osz, outline=color, fill=color) self.c.create_text(xp + 8, yp, text=str(yr), anchor=W) i1 += 1 # And, add a title. tistr = 'Vehicle count: %d' % self.vcount if self.fcount != self.vcount: tistr = 'Filtered vehicle count: %d' % self.fcount xp = (self.x_grid[0] + self.x_grid[-1]) / 2 yp = self.y_grid[-1] - 30 self.c.create_text(xp, yp, text=tistr, font=('Helvetica', '16')) zstr1 = 'Click on a blank graph location to zoom in' zstr2 = 'Right click to zoom out' if self.zoom_level == 0: zstr = zstr1 elif self.zoom_level == 2: zstr = zstr2 else: zstr = zstr1 + ', or r' + zstr2[1:] self.c.create_text(xp, yp + 16, text=zstr, font=('Helvetica', '14')) def mouseover(self, event): oval = event.widget.find_closest(event.x, event.y)[0] # XXX Sometimes, the closest widget is an axis grid line, not an oval. # Need to handle this correctly eventually. if oval not in self.ov_dict: return self.is_hovering = True ind, x, y, color, filtered = self.ov_dict[oval] # Figure out how high the box needs to be by creating the text off- # graph, then getting its bbox and deleting it. w = 200 de_text = self.u.all_descr[ind] deobj = self.c.create_text(self.w + 3, self.h + 3, text=de_text, anchor=N+W, width=w-6, font=('Helvetica', '14')) bbox = self.c.bbox(deobj) self.c.delete(deobj) h = 18 + bbox[3] - bbox[1] border = 5 if x > self.xmid: x -= (w + border) else: x += border if y > self.ymid: y -= (h + border) else: y += border self.re = list() self.re.append(self.c.create_rectangle(x, y, x + w, y + h, fill=resaturate(color, 50))) pr_text = '$%s' % self.u.commafy(self.u.all_price[ind]) self.re.append(self.c.create_text(x + 3, y + 3, text=pr_text, anchor=N+W, font=('Helvetica', '10'))) km_text = '%skm' % self.u.commafy(self.u.all_km[ind]) self.re.append(self.c.create_text(x + w - 3, y + 3, text=km_text, anchor=N+E, font=('Helvetica', '10'))) wh_text = self.u.all_wherestr[ind] if wh_text[0].isdigit(): wh_text += ' away' self.re.append(self.c.create_text(x + w/2, y + 3, text=wh_text, anchor=N, font=('Helvetica', '10'))) self.re.append(self.c.create_text(x + 3, y + 16, text=de_text, anchor=N+W, width=w-6, font=('Helvetica', '14'))) def set_filter(self, st): "Given a string 'st', filter ovals whose description doesn't match." self.filter = st self.replot() def mouseoff(self, event): "Code for mousing off a data point." # The tooptip rectangle and all its sub-objects need to be destroyed. map(self.c.delete, self.re) # Also, need to note that we're no longer over an oval -- that way, # Button-1 events will cause a zoom, rather than launching a web page. self.is_hovering = False def _zoom_animation(self): import time from math import tanh scale = 5 for i1 in range(-scale, scale+1): self.replot(zlfrac=0.5 + 0.5*tanh(i1*2.0/scale)/tanh(2.0)) self.c.update() def zoom(self, event): # Only zoom in if we're actually within the graph boundaries. if event.x <= self.x_grid[0] or event.x > self.x_grid[-1]: return if event.y >= self.y_grid[0] or event.y < self.y_grid[-1]: return # Don't zoom if we're hovering over a data point: let the web browser # event handler operate. if self.is_hovering: return # Don't zoom in more than twice. if self.zoom_level >= 2: return # Find the grid square which we're inside. for i1 in range(len(self.x_grid) - 1): if event.x <= self.x_grid[i1 + 1]: xgrid = i1 + 1 break for i1 in range(len(self.y_grid) - 1): if event.y >= self.y_grid[i1 + 1]: ygrid = i1 + 1 break self.zoom_level += 1 zl = self.zoom_level # Make the limits of the new graph be those of the grid square which # was clicked inside. self.km[zl] = (self.xtick[xgrid-1], self.xtick[xgrid]) self.price[zl] = (self.ytick[ygrid-1], self.ytick[ygrid]) if zl == 1: self.zoom_price_start = self.u.axis(*self.price[0])[:2] self.zoom_km_start = self.u.axis(*self.km[0])[:2] else: self.zoom_price_start = self.price[zl - 1] self.zoom_km_start = self.km[zl - 1] self.zoom_price_end = self.price[zl] self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def unzoom(self, event): # If already at maximum zoom, nothing to be done. if self.zoom_level == 0: return # If not clicking inside graph boundaries, don't unzoom. if event.x <= self.x_grid[0] or event.x > self.x_grid[-1]: return if event.y >= self.y_grid[0] or event.y < self.y_grid[-1]: return self.zoom_level -= 1 zl = self.zoom_level self.zoom_price_start = self.price[zl + 1] self.zoom_km_start = self.km[zl + 1] if zl == 0: self.zoom_price_end = self.u.axis(*self.price[0])[:2] self.zoom_km_end = self.u.axis(*self.km[0])[:2] else: self.zoom_price_end = self.price[zl] self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def left_key(self, event): zl = self.zoom_level if zl == 0: return # If at left edge already, don't scroll. kz = self.km[zl] if self.km[0][0] > kz[0]: return self.zoom_price_start = self.zoom_price_end = self.price[zl] self.zoom_km_start = kz self.km[zl] = (kz[0] - (kz[1] - kz[0]), kz[0]) self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def right_key(self, event): zl = self.zoom_level if zl == 0: return # If at right edge already, don't scroll. kz = self.km[zl] if self.km[0][1] < kz[1]: return self.zoom_price_start = self.zoom_price_end = self.price[zl] self.zoom_km_start = kz self.km[zl] = (kz[1], kz[1] + (kz[1] - kz[0])) self.zoom_km_end = self.km[zl] self._zoom_animation() self.replot() def down_key(self, event): zl = self.zoom_level if zl == 0: return # If at bottom edge already, don't scroll. pz = self.price[zl] if self.price[0][0] > pz[0]: return self.zoom_km_start = self.zoom_km_end = self.km[zl] self.zoom_price_start = pz self.price[zl] = (pz[0] - (pz[1] - pz[0]), pz[0]) self.zoom_price_end = self.price[zl] self._zoom_animation() self.replot() def up_key(self, event): zl = self.zoom_level if zl == 0: return # If at top edge already, don't scroll. pz = self.price[zl] if self.price[0][1] < pz[1]: return self.zoom_km_start = self.zoom_km_end = self.km[zl] self.zoom_price_start = pz self.price[zl] = (pz[1], pz[1] + (pz[1] - pz[0])) self.zoom_price_end = self.price[zl] self._zoom_animation() self.replot() def select(self, event): "Open a web page, when a data point has been clicked on." oval = event.widget.find_closest(event.x, event.y)[0] # XXX As above, sometimes the closest widget is a grid line, not an # oval. Need to handle this correctly, eventually. if oval not in self.ov_dict: return ind, xp, yp, color, filtered = self.ov_dict[oval] webbrowser.open(self.u.all_alink[ind])
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 TopLevelView(BaseView): TEXT_LABEL_OFFSET = 35 ROD_BOX_START_X = 50 ROD_BOX_START_Y = 45 ROD_BOX_WIDTH = 70 ROD_BOX_HEIGHT = 25 ROD_BOX_VERTICAL_OFFSET = 10 # other values are the same like for the ROD boxes ROS_BOX_START_X = 150 ROS_BOX_START_Y = 45 def __init__(self, tkRoot, systems, app, logger): BaseView.__init__(self, tkRoot, app, logger) self.systems = systems # names of all subsystems # Canvas options width, height seem not necessary ... self.canvas = Canvas(self.tkRoot, bg=BaseView.CANVAS_BG_COLOR) self.canvas.pack(expand=YES, fill=BOTH) self.__createRodBoxes() self.__createRosBoxes() self.__createRodRosLines() self.name = "TOP" # name of this special view self.app.addActiveView(self.name, self) # special top view # add logo logo = PhotoImage(file="doc/logo.gif") self.canvas.create_image(conf.GUI_WIDTH - 20, conf.GUI_HEIGHT - 20, image=logo) self.canvas.image = logo # keep a reference, otherwise it won't appear def __openRosDetailedView(self, comp): try: rosTree = self.app.rosTrees[comp.name] # every view (except for TopLevelView) has a tree node # acting as root for such particular view treeRootNode = rosTree.root v = View(self.tkRoot, self.app, comp, treeRootNode, self.logger) v.createContent() self.logger.debug("View created for '%s'" % comp) except KeyError: m = "ROS data n/a for '%s'" % comp self.logger.warn(m) tkMessageBox.showwarning("Quit", m, parent=self.tkRoot) def openDetailedView(self, comp): if comp.group == "ROS": # check if view which is to be open has not been opened already if self.app.isViewActive(comp.name): m = ("View '%s' is already among active windows, " "not created." % comp.name) self.logger.warn(m) tkMessageBox.showwarning("Quit", m, parent=self.tkRoot) else: self.__openRosDetailedView(comp) else: m = "No view available for '%s' " % comp self.logger.warn(m) tkMessageBox.showwarning("Quit", m, parent=self.tkRoot) def __createRodBoxes(self): state = NotAssociated() group = "ROD" # title self.canvas.create_text( TopLevelView.ROD_BOX_START_X + TopLevelView.TEXT_LABEL_OFFSET, TopLevelView.ROD_BOX_START_Y - TopLevelView.ROD_BOX_HEIGHT, text=group, font=self.bigFont) for i in range(len(self.systems)): x0 = TopLevelView.ROD_BOX_START_X y0 = (TopLevelView.ROD_BOX_START_Y + (i * (TopLevelView.ROD_BOX_HEIGHT + TopLevelView.ROD_BOX_VERTICAL_OFFSET))) x1 = x0 + TopLevelView.ROD_BOX_WIDTH y1 = y0 + TopLevelView.ROD_BOX_HEIGHT box = Box(group, self.systems[i], self.canvas) box.create(x0, y0, x1, y1, state.color) box.text(x0 + TopLevelView.TEXT_LABEL_OFFSET, y0 + TopLevelView.ROD_BOX_HEIGHT / 2, self.systems[i], self.boxTitleFont) self.compStore.append(box) def __createRosBoxes(self): state = Inactive() group = "ROS" # title self.canvas.create_text( TopLevelView.ROS_BOX_START_X + TopLevelView.TEXT_LABEL_OFFSET, TopLevelView.ROS_BOX_START_Y - TopLevelView.ROD_BOX_HEIGHT, text=group, font=self.bigFont) for i in range(len(self.systems)): # need to get a tree node controlling the created component try: rosTree = self.app.rosTrees[self.systems[i]] node = rosTree.root box = RenderableBox(group, self.canvas, node) except KeyError: # some defined system don't have data (node) to be controlled by box = Box(group, self.systems[i], self.canvas) x0 = TopLevelView.ROS_BOX_START_X y0 = (TopLevelView.ROS_BOX_START_Y + (i * (TopLevelView.ROD_BOX_HEIGHT + TopLevelView.ROD_BOX_VERTICAL_OFFSET))) x1 = x0 + TopLevelView.ROD_BOX_WIDTH y1 = y0 + TopLevelView.ROD_BOX_HEIGHT idBox = box.create(x0, y0, x1, y1, state.color) idText = box.text(x0 + TopLevelView.TEXT_LABEL_OFFSET, y0 + TopLevelView.ROD_BOX_HEIGHT / 2, self.systems[i], self.boxTitleFont) dch = DoubleClickHandler(box, self) # bind both IDs - box and text in the box self.canvas.tag_bind(idBox, "<Double-1>", dch) self.canvas.tag_bind(idText, "<Double-1>", dch) self.compStore.append(box) def __createRodRosLines(self): group = "RODROSLINE" for i in range(len(self.systems)): x0 = TopLevelView.ROD_BOX_START_X + TopLevelView.ROD_BOX_WIDTH y0 = (TopLevelView.ROS_BOX_START_Y + (i * (TopLevelView.ROD_BOX_HEIGHT + TopLevelView.ROD_BOX_VERTICAL_OFFSET)) + TopLevelView.ROD_BOX_HEIGHT / 2) x1 = TopLevelView.ROS_BOX_START_X y1 = y0 line = Line(group, self.systems[i], self.canvas) line.create(x0, y0, x1, y1) self.compStore.append(line)
class histogramWidget: BACKGROUND = "#222222" EDGE_HISTOGRAM_COLOR = "#999999" NODE_HISTOGRAM_COLOR = "#555555" TOOLTIP_COLOR="#FFFF55" PADDING = 8 CENTER_WIDTH = 1 CENTER_COLOR = "#444444" ZERO_GAP = 1 UPDATE_WIDTH = 9 UPDATE_COLOR = "#FFFFFF" HANDLE_WIDTH = 5 HANDLE_COLOR = "#FFFFFF" HANDLE_LENGTH = (HEIGHT-2*PADDING) TICK_COLOR = "#FFFFFF" TICK_WIDTH = 10 TICK_FACTOR = 2 LOG_BASE = 10.0 def __init__(self, parent, x, y, width, height, data, logScale=False, callback=None): self.canvas = Canvas(parent,background=histogramWidget.BACKGROUND, highlightbackground=histogramWidget.BACKGROUND,width=width,height=height) self.canvas.place(x=x,y=y,width=width,height=height,bordermode="inside") self.logScale = logScale self.callback = callback self.edgeBars = [] self.nodeBars = [] self.binValues = [] self.numBins = len(data) - 1 self.currentBin = self.numBins # start the slider at the highest bin edgeRange = 0.0 nodeRange = 0.0 for values in data.itervalues(): if values[0] > edgeRange: edgeRange = values[0] if values[1] > nodeRange: nodeRange = values[1] edgeRange = float(edgeRange) # ensure that it will yield floats when used in calculations... nodeRange = float(nodeRange) if logScale: edgeRange = math.log(edgeRange,histogramWidget.LOG_BASE) nodeRange = math.log(nodeRange,histogramWidget.LOG_BASE) # calculate the center line - but don't draw it yet self.center_x = histogramWidget.PADDING if self.logScale: self.center_x += histogramWidget.TICK_WIDTH+histogramWidget.PADDING self.center_y = height/2 self.center_x2 = width-histogramWidget.PADDING self.center_y2 = self.center_y + histogramWidget.CENTER_WIDTH # draw the histograms with background-colored baseline rectangles (these allow tooltips to work on very short bars with little area) self.bar_interval = float(self.center_x2 - self.center_x) / (self.numBins+1) bar_x = self.center_x edge_y2 = self.center_y-histogramWidget.PADDING edge_space = edge_y2-histogramWidget.PADDING node_y = self.center_y2+histogramWidget.PADDING node_space = (height-node_y)-histogramWidget.PADDING thresholds = sorted(data.iterkeys()) for threshold in thresholds: self.binValues.append(threshold) edgeWeight = data[threshold][0] nodeWeight = data[threshold][1] if logScale: if edgeWeight > 0: edgeWeight = math.log(edgeWeight,histogramWidget.LOG_BASE) else: edgeWeight = 0 if nodeWeight > 0: nodeWeight = math.log(nodeWeight,histogramWidget.LOG_BASE) else: nodeWeight = 0 bar_x2 = bar_x + self.bar_interval edge_y = histogramWidget.PADDING + int(edge_space*(1.0-edgeWeight/edgeRange)) edge = self.canvas.create_rectangle(bar_x,edge_y,bar_x2,edge_y2,fill=histogramWidget.EDGE_HISTOGRAM_COLOR,width=0) baseline = self.canvas.create_rectangle(bar_x,edge_y2+histogramWidget.ZERO_GAP,bar_x2,edge_y2+histogramWidget.PADDING,fill=histogramWidget.BACKGROUND,width=0) self.canvas.addtag_withtag("Threshold: %f" % threshold,edge) self.canvas.addtag_withtag("No. Edges: %i" % data[threshold][0],edge) self.canvas.tag_bind(edge,"<Enter>",self.updateToolTip) self.canvas.tag_bind(edge,"<Leave>",self.updateToolTip) self.edgeBars.append(edge) self.canvas.addtag_withtag("Threshold: %f" % threshold,baseline) self.canvas.addtag_withtag("No. Edges: %i" % data[threshold][0],baseline) self.canvas.tag_bind(baseline,"<Enter>",self.updateToolTip) self.canvas.tag_bind(baseline,"<Leave>",self.updateToolTip) node_y2 = node_y + int(node_space*(nodeWeight/nodeRange)) node = self.canvas.create_rectangle(bar_x,node_y,bar_x2,node_y2,fill=histogramWidget.NODE_HISTOGRAM_COLOR,width=0) baseline = self.canvas.create_rectangle(bar_x,node_y-histogramWidget.PADDING,bar_x2,node_y-histogramWidget.ZERO_GAP,fill=histogramWidget.BACKGROUND,width=0) self.canvas.addtag_withtag("Threshold: %f" % threshold,node) self.canvas.addtag_withtag("No. Nodes: %i" % data[threshold][1],node) self.canvas.tag_bind(node,"<Enter>",self.updateToolTip) self.canvas.tag_bind(node,"<Leave>",self.updateToolTip) self.nodeBars.append(node) self.canvas.addtag_withtag("Threshold: %f" % threshold,baseline) self.canvas.addtag_withtag("No. Nodes: %i" % data[threshold][1],baseline) self.canvas.tag_bind(baseline,"<Enter>",self.updateToolTip) self.canvas.tag_bind(baseline,"<Leave>",self.updateToolTip) bar_x = bar_x2 # now draw the center line self.centerLine = self.canvas.create_rectangle(self.center_x,self.center_y,self.center_x2,self.center_y2,fill=histogramWidget.CENTER_COLOR,width=0) # draw the tick marks if logarithmic if self.logScale: tick_x = histogramWidget.PADDING tick_x2 = histogramWidget.PADDING+histogramWidget.TICK_WIDTH start_y = edge_y2 end_y = histogramWidget.PADDING dist = start_y-end_y while dist > 1: dist /= histogramWidget.TICK_FACTOR self.canvas.create_rectangle(tick_x,end_y+dist-1,tick_x2,end_y+dist,fill=histogramWidget.TICK_COLOR,width=0) start_y = node_y end_y = height-histogramWidget.PADDING dist = end_y-start_y while dist > 1: dist /= histogramWidget.TICK_FACTOR self.canvas.create_rectangle(tick_x,end_y-dist,tick_x2,end_y-dist+1,fill=histogramWidget.TICK_COLOR,width=0) # draw the update bar bar_x = self.currentBin*self.bar_interval + self.center_x bar_x2 = self.center_x2 bar_y = self.center_y-histogramWidget.UPDATE_WIDTH/2 bar_y2 = bar_y+histogramWidget.UPDATE_WIDTH self.updateBar = self.canvas.create_rectangle(bar_x,bar_y,bar_x2,bar_y2,fill=histogramWidget.UPDATE_COLOR,width=0) # draw the handle handle_x = self.currentBin*self.bar_interval-histogramWidget.HANDLE_WIDTH/2+self.center_x handle_x2 = handle_x+histogramWidget.HANDLE_WIDTH handle_y = self.center_y-histogramWidget.HANDLE_LENGTH/2 handle_y2 = handle_y+histogramWidget.HANDLE_LENGTH self.handleBar = self.canvas.create_rectangle(handle_x,handle_y,handle_x2,handle_y2,fill=histogramWidget.HANDLE_COLOR,width=0) self.canvas.tag_bind(self.handleBar, "<Button-1>",self.adjustHandle) self.canvas.tag_bind(self.handleBar, "<B1-Motion>",self.adjustHandle) self.canvas.tag_bind(self.handleBar, "<ButtonRelease-1>",self.adjustHandle) parent.bind("<Left>",lambda e: self.nudgeHandle(e,-1)) parent.bind("<Right>",lambda e: self.nudgeHandle(e,1)) # init the tooltip as nothing self.toolTipBox = self.canvas.create_rectangle(0,0,0,0,state="hidden",fill=histogramWidget.TOOLTIP_COLOR,width=0) self.toolTip = self.canvas.create_text(0,0,state="hidden",anchor="nw") self.canvas.bind("<Enter>",self.updateToolTip) self.canvas.bind("<Leave>",self.updateToolTip) def adjustHandle(self, event): newBin = int(self.numBins*(event.x-self.center_x)/float(self.center_x2-self.center_x)+0.5) if newBin == self.currentBin or newBin < 0 or newBin > self.numBins: return self.canvas.move(self.handleBar,(newBin-self.currentBin)*self.bar_interval,0) self.currentBin = newBin if self.callback != None: self.callback(self.binValues[newBin]) def nudgeHandle(self, event, distance): temp = self.currentBin+distance if temp < 0 or temp > self.numBins: return self.canvas.move(self.handleBar,distance*self.bar_interval,0) self.currentBin += distance if self.callback != None: self.callback(self.binValues[self.currentBin]) def update(self, currentBins): currentBar = self.canvas.coords(self.updateBar) self.canvas.coords(self.updateBar,currentBins*self.bar_interval+self.center_x,currentBar[1],currentBar[2],currentBar[3]) def updateToolTip(self, event): allTags = self.canvas.gettags(self.canvas.find_overlapping(event.x,event.y,event.x+1,event.y+1)) if len(allTags) == 0: self.canvas.itemconfig(self.toolTipBox,state="hidden") self.canvas.itemconfig(self.toolTip,state="hidden") return outText = "" for t in allTags: if t == "current": continue outText += t + "\n" outText = outText[:-1] # strip the last return self.canvas.coords(self.toolTip,event.x+20,event.y) self.canvas.itemconfig(self.toolTip,state="normal",text=outText,anchor="nw") # correct if our tooltip is off screen textBounds = self.canvas.bbox(self.toolTip) if textBounds[2] >= WIDTH-2*histogramWidget.PADDING: self.canvas.itemconfig(self.toolTip, anchor="ne") self.canvas.coords(self.toolTip,event.x-20,event.y) if textBounds[3] >= HEIGHT-2*histogramWidget.PADDING: self.canvas.itemconfig(self.toolTip, anchor="se") elif textBounds[3] >= HEIGHT-2*histogramWidget.PADDING: self.canvas.itemconfig(self.toolTip, anchor="sw") # draw the box behind it self.canvas.coords(self.toolTipBox,self.canvas.bbox(self.toolTip)) self.canvas.itemconfig(self.toolTipBox, state="normal")
class Palette(Frame): """ Tkinter Frame that displays all items that can be added to the board. """ def __init__(self, parent, board, width=PALETTE_WIDTH, height=PALETTE_HEIGHT): """ |board|: the board on to which items will be added from this palette. |width|: the width of this palette. |height|: the height of this palette. """ assert isinstance(board, Board), 'board must be a Board' Frame.__init__(self, parent, background=PALETTE_BACKGROUND_COLOR) self.board = board # canvas on which items are displayed self.canvas = Canvas(self, width=width, height=height, highlightthickness=0, background=PALETTE_BACKGROUND_COLOR) self.width = width self.height = height # x-position of last item added on the LEFT side of this palette self.current_left_x = PALETTE_PADDING # x-position of last item added on the RIGHT side of this palette self.current_right_x = self.width # line to separate left and right sides of palette self.left_right_separation_line_id = None # setup ui self.canvas.pack() self.pack() def _add_item_callback(self, drawable_type, desired_offset_x, **kwargs): """ Returns a callback method that, when called, adds an item of the given |drawable_type| to the board, at the given |desired_offset_x|. """ assert issubclass(drawable_type, Drawable), ('drawable must be a Drawable ' 'subclass') def callback(event): # clear current message on the board, if any self.board.remove_message() # create new drawable new_drawable = drawable_type(**kwargs) desired_offset_y = (self.board.height - new_drawable.height - PALETTE_PADDING) desired_offset = (desired_offset_x, desired_offset_y) self.board.add_drawable(new_drawable, desired_offset) return new_drawable return callback def _spawn_types_callback(self, types_to_add, desired_offset_x): """ Returns a callback method that, when called, adds the items given in |types_to_add| to the board, starting at the given |desired_offset_x|. """ assert isinstance(types_to_add, list), 'types_to_add must be a list' def callback(event): dx = 0 # assign the same color and group_id to the types being spawned color = '#%02X%02X%02X' % (randint(0, 200), randint( 0, 200), randint(0, 200)) group_id = int(round(time() * 1000)) num_drawables_added = 0 for add_type, add_kwargs in types_to_add: add_kwargs['color'] = color add_kwargs['group_id'] = group_id new_drawable = self._add_item_callback(add_type, desired_offset_x + dx, **add_kwargs)(event) if new_drawable: num_drawables_added += 1 dx += new_drawable.width + BOARD_GRID_SEPARATION if num_drawables_added > 1: self.board._action_history.combine_last_n(num_drawables_added) return callback def add_drawable_type(self, drawable_type, side, callback, types_to_add=None, **kwargs): """ Adds a drawable type for display on this palette. |drawable_type|: a subclass of Drawable to display. |side|: the side of this palette on which to put the display (LEFT or RIGHT). |callback|: method to call when display item is clicked. If None, the default callback adds an item of the display type to the board. |types_to_add|: a list of Drawables to add to the board when this item is clicked on the palette, or None if such a callback is not desired. |**kwargs|: extra arguments needed to initialize the drawable type. """ assert issubclass(drawable_type, Drawable), ('drawable must be a Drawable ' 'subclass') # create a sample (display) drawable display = drawable_type(**kwargs) # draw the display on the appropriate side of the palette if side == RIGHT: offset_x = self.current_right_x - PALETTE_PADDING - display.width self.current_right_x -= display.width + PALETTE_PADDING # update left-right separation line if self.left_right_separation_line_id: self.canvas.delete(self.left_right_separation_line_id) separation_x = self.current_right_x - PALETTE_PADDING self.left_right_separation_line_id = self.canvas.create_line( separation_x, 0, separation_x, self.height, fill=PALETTE_SEPARATION_LINE_COLOR) else: # if given |side| is illegal, assume LEFT offset_x = self.current_left_x + PALETTE_PADDING self.current_left_x += PALETTE_PADDING + display.width + PALETTE_PADDING offset_y = (self.height - display.height) / 2 if offset_y % BOARD_GRID_SEPARATION: offset_y = (offset_y / BOARD_GRID_SEPARATION) * BOARD_GRID_SEPARATION offset = (offset_x, offset_y) display.draw_on(self.canvas, offset) display.draw_connectors(self.canvas, offset) # attach callback to drawn parts # default callback adds items of this drawable type to the board if callback is None: callback = self._add_item_callback( drawable_type, offset_x, **kwargs) if ( types_to_add is None) else self._spawn_types_callback( types_to_add, offset_x) else: assert types_to_add is None, ( 'if callback is provided, types_to_add ' 'will not be used') # bind callback for part in display.parts: self.canvas.tag_bind(part, '<Button-1>', callback) self.canvas.tag_bind(part, '<Double-Button-1>', callback) for connector in display.connectors: self.canvas.tag_bind(connector.canvas_id, '<Button-1>', callback) self.canvas.tag_bind(connector.canvas_id, '<Double-Button-1>', callback) return display def draw_separator(self): """ Draws a separation line at the current left position. """ self.canvas.create_line(self.current_left_x, 0, self.current_left_x, self.height, fill=PALETTE_SEPARATION_LINE_COLOR) self.current_left_x += PALETTE_PADDING
class View(BaseView): TEXT_LABEL_OFFSET = 50 BOX_START_X = 30 BOX_START_Y = 45 BOX_WIDTH = 100 BOX_HEIGHT = 45 BOX_VERTICAL_OFFSET = 15 ANOTHER_GUI_OFF = 30 GUI_WIDTH = 170 def __init__(self, tkRoot, app, masterComp, parentNode, logger): BaseView.__init__(self, tkRoot, app, logger) # master (upper level view) component on which this view was issued self.masterComp = masterComp self.parentNode = parentNode # open detailed window - will go into View class, create Canvas in this window self.window = Toplevel(self.tkRoot) # self.window.bind("<<close-window>>", self.__onClose) # doesn't work self.window.protocol("WM_DELETE_WINDOW", self.__onClose) self.window.title("%s" % self.masterComp.name) self.canvas = Canvas(self.window, bg=BaseView.CANVAS_BG_COLOR) self.canvas.pack(expand=YES, fill=BOTH) self.app.addActiveView(self.masterComp.name, self) def createContent(self): self.__setWindowGeometry() self.__setScrollableView() self.__createBoxes() def __setScrollableView(self): """Sets a scrollbar and scroll area corresponding to number of boxes the views needs to accommodate. """ oneBoxSpace = View.BOX_HEIGHT + View.BOX_VERTICAL_OFFSET vertNeeded = len( self.parentNode.children) * oneBoxSpace + View.BOX_START_Y if (View.BOX_START_Y + vertNeeded) < conf.GUI_HEIGHT: return self.logger.debug("View needs to be scrollable, setting ...") self._setScrollBar(vertNeeded) def _setScrollBar(self, verticalSpace): """Derived class RosView calls this method.""" self.canvas.config(scrollregion=(0, 0, 200, verticalSpace)) self.canvas.config(highlightthickness=0) # no pixels to border sbar = Scrollbar(self.canvas) sbar.config(command=self.canvas.yview) # xlink sbar and canv self.canvas.config(yscrollcommand=sbar.set) # move one moves other sbar.pack(side=RIGHT, fill=Y) # pack first=clip last self.canvas.pack(side=LEFT, expand=YES, fill=BOTH) # canvas clipped first def __createBoxes(self): # always be the same as self.masterComp.name (title and label must agree) group = self.parentNode.name # self.masterComp.name self.canvas.create_text(View.BOX_START_X + View.TEXT_LABEL_OFFSET, View.BOX_START_Y - View.BOX_VERTICAL_OFFSET, text=group, font=self.bigFont) nodeKeys = self.parentNode.children.keys() nodeKeys.sort() # sort the keys alphabetically for i in range(len(nodeKeys)): node = self.parentNode.children[nodeKeys[i]] x0 = View.BOX_START_X y0 = (View.BOX_START_Y + (i * (View.BOX_HEIGHT + View.BOX_VERTICAL_OFFSET))) x1 = x0 + View.BOX_WIDTH y1 = y0 + View.BOX_HEIGHT box = RenderableBox(self.masterComp.name, self.canvas, node) dch = DoubleClickHandler(box, self) idBox = box.create(x0, y0, x1, y1, node.state.color) idText = box.text(x0 + View.TEXT_LABEL_OFFSET, y0 + View.BOX_HEIGHT / 2, node.name, self.boxTitleFont) # bind both IDs - box and text in the box self.canvas.tag_bind(idBox, "<Double-1>", dch) self.canvas.tag_bind(idText, "<Double-1>", dch) # could even perhaps be list and not a dictionary self.compStore.append(box) def openDetailedView(self, comp): # check if view which is to be open has not been opened already if self.app.isViewActive(comp.name): m = ("View '%s' is already among active windows, " "not created." % comp.name) self.logger.warn(m) tkMessageBox.showwarning("Quit", m, parent=self.window) return try: rootNode = self.parentNode.children[comp.name] except KeyError: self.logger.error("Could not access child node for '%s'" % comp.name) else: V = rootNode.viewerForChildren v = V(self.tkRoot, self.app, comp, rootNode, self.logger) v.createContent() self.logger.debug("View created: name '%s' root node: " "'%s'" % (comp.name, rootNode.name)) def __onClose(self): self.app.removeActiveView(self.masterComp.name) self.window.destroy() def __setWindowGeometry(self): offsetX = View.ANOTHER_GUI_OFF * self.app.activeViewsCount() x = conf.GUI_OFF_X + conf.GUI_WIDTH + offsetX geom = "%sx%s+%s+%s" % (View.GUI_WIDTH, conf.GUI_HEIGHT, x, conf.GUI_OFF_Y) self.window.geometry(geom)
class Proto_Board_Visualizer(Frame): """ Tk Frame to visualize Proto boards. """ def __init__(self, parent, proto_board, show_pwr_gnd_pins): """ |proto_board|: the proto board to visualize. |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to connect to a power supply. """ Frame.__init__(self, parent, background=BACKGROUND_COLOR) self._parent = parent self._parent.title(WINDOW_TITLE) self._parent.resizable(0, 0) self._proto_board = proto_board self._show_pwr_gnd_pins = show_pwr_gnd_pins self._canvas = Canvas(self, width=WIDTH, height=HEIGHT, background=BACKGROUND_COLOR) self._tooltip_helper = Tooltip_Helper(self._canvas) self._wire_parts = {} # state for outline highlighing self._piece_outline_id = None self._wire_outline_ids = [] self._piece_highlight = lambda labels: None self._wire_highlight = lambda label: None self._setup_bindings() self._setup_menu() self._draw_proto_board() def _point_inside_piece(self, piece, x, y): """ Returns True if the point (|x|, |y|) is on the gien |piece|. """ r1, c1, r2, c2 = piece.bbox() x1, y1 = self._rc_to_xy((r1, c1)) x2, y2 = self._rc_to_xy((r2, c2)) return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE def _maybe_show_tooltip(self, event): """ Shows a tooltip of the respective node if the cursor is on a wire or a valid location on the proto board, or the respective piece label if the cursor is on a piece. """ # check if cursor is on a wire item = self._canvas.find_closest(event.x, event.y)[0] if item in self._wire_parts: self._wire_highlight(self._wire_parts[item]) return # check if cursor is on a piece for piece in self._proto_board.get_pieces(): if self._point_inside_piece(piece, event.x, event.y): self._tooltip_helper.show_tooltip( event.x, event.y, 'ID: %s' % piece.label, background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND) self._piece_highlight( piece.labels_at((event.x, event.y), self._rc_to_xy(piece.top_left_loc))) return self._piece_highlight([]) # check if cursor is on a valid proto board location loc = self._xy_to_rc(event.x, event.y) if loc: node = self._proto_board.node_for(loc) if node: self._wire_highlight(node) return self._wire_highlight(None) # if none of the above, remove previous tooltip, if any self._tooltip_helper.hide_tooltip() def _setup_bindings(self): """ Sets up event bindings. """ self._canvas.bind('<Motion>', self._maybe_show_tooltip) def _rc_to_xy(self, loc): """ Returns the top left corner of the connector located at row |r| column |c|. """ r, c = loc x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + ( num_vertical_separators(r) * (VERTICAL_SEPARATION - CONNECTOR_SPACING)) return x, y def _xy_to_rc(self, x, y): """ Returns the row and column of the valid location on the proto board containing the point (|x|, |y|), or None if no such location exists. """ for r in xrange(PROTO_BOARD_HEIGHT): for c in xrange(PROTO_BOARD_WIDTH): if valid_loc((r, c)): x1, y1 = self._rc_to_xy((r, c)) x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE if x1 <= x <= x2 and y1 <= y <= y2: return r, c return None def _draw_connector(self, x, y, fill=CONNECTOR_COLOR, outline=CONNECTOR_COLOR): """ Draws a connector at the given coordinate and with the given colors. """ self._canvas.create_rectangle(x, y, x + CONNECTOR_SIZE, y + CONNECTOR_SIZE, fill=fill, outline=outline) def _draw_connectors(self): """ Draws the connectors on the proto board. """ for r in ROWS: for c in COLUMNS: if valid_loc((r, c)): self._draw_connector(*self._rc_to_xy((r, c))) def _draw_bus_line(self, y, color): """ Draws a bus line at the given horizontal position |y| and with the given |color|. """ x_1 = self._rc_to_xy((0, 1))[0] x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0] self._canvas.create_line(x_1, y, x_2, y, fill=color) def _draw_bus_lines(self): """ Draws all four bus lines on the proto board. """ offset = 10 self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR) self._draw_bus_line( self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset, POSITIVE_COLOR) self._draw_bus_line( self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - (offset), NEGATIVE_COLOR) self._draw_bus_line( self._rc_to_xy( (PROTO_BOARD_HEIGHT - 1, 0))[1] + (CONNECTOR_SIZE + offset), POSITIVE_COLOR) def _draw_labels(self): """ Draws the row and column labels. """ # row labels row_labels = dict( zip(range(11, 1, -1), ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'])) for r in filter(lambda r: r in row_labels, ROWS): self._canvas.create_text(self._rc_to_xy((r, -1)), text=row_labels[r]) x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH)) self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r]) # columns labels h_offset = 2 v_offset = 10 for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS): x_1, y_1 = self._rc_to_xy((2, c)) self._canvas.create_text(x_1 + h_offset, y_1 - v_offset, text=(c + 1)) x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c)) self._canvas.create_text(x_2 + h_offset, y_2 + CONNECTOR_SIZE + v_offset, text=(c + 1)) def _draw_gnd_pwr_pins(self): """ Draws pins to show the ground and power rails. """ # pin positions g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3)) p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3)) # big rectangles large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING small_h_offset = 2 large_v_offset = 7 small_v_offset = 3 self._canvas.create_rectangle(g_x - small_h_offset, g_y - large_v_offset, g_x + large_h_offset, g_y + CONNECTOR_SIZE + small_v_offset, fill=NEGATIVE_COLOR) self._canvas.create_rectangle(p_x - small_h_offset, p_y - small_v_offset, p_x + large_h_offset, p_y + CONNECTOR_SIZE + large_v_offset, fill=POSITIVE_COLOR) # connectors self._draw_connector(g_x, g_y, outline='black') self._draw_connector(p_x, p_y, outline='black') # text text_v_offset = 2 text_h_offset = 17 self._canvas.create_text(g_x + text_h_offset, g_y - text_v_offset, text='gnd', fill='white') self._canvas.create_text(p_x + text_h_offset, p_y + text_v_offset, text='+10', fill='white') def _draw_piece(self, piece): """ Draws the given circuit |piece| on the canvas. """ piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc)) def _draw_pieces(self): """ Draws all the pieces on the proto board. """ for piece in self._proto_board.get_pieces(): self._draw_piece(piece) def _draw_wire(self, wire): """ Draws the given |wire| on the canvas. """ x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) length = int(ceil(wire.length())) fill = 'white' if 1 < length < 10: fill = WIRE_COLORS[length] elif 10 <= length < 50: fill = WIRE_COLORS[(length + 9) / 10] def draw_wire(parts=None): if parts: for part in parts: self._canvas.delete(part) del self._wire_parts[part] new_parts = [ self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=WIRE_OUTLINE, width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=fill, width=3, capstyle='round') ] for part in new_parts: self._canvas.tag_bind(part, '<Button-1>', lambda event: draw_wire(new_parts)) self._wire_parts[part] = wire.node draw_wire() def _draw_wires(self): """ Draws all the wires on the proto board. """ for wire in sorted(self._proto_board.get_wires(), key=lambda wire: -wire.length()): self._draw_wire(wire) def _redraw_wires(self): """ Erases then redraws the wires on the protoboard. """ for part in self._wire_parts: self._canvas.delete(part) self._wire_parts.clear() self._draw_wires() def _draw_proto_board(self): """ Draws the given |proto_board|. """ self._draw_connectors() self._draw_bus_lines() self._draw_labels() self._draw_pieces() self._draw_wires() if self._show_pwr_gnd_pins: self._draw_gnd_pwr_pins() self._canvas.pack() self.pack() def _wire_to_cmax_str(self, wire): """ Returns a CMax representation (when saved in a file) of the given |wire|. """ c1, r1 = loc_to_cmax_rep(wire.loc_1) c2, r2 = loc_to_cmax_rep(wire.loc_2) return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2) def _to_cmax_str(self): """ Returns a string CMax representation of the proto board we are visualizing. """ # header lines = ['#CMax circuit'] # wires for wire in self._proto_board.get_wires(): lines.append(self._wire_to_cmax_str(wire)) # circuit pieces for piece in self._proto_board.get_pieces(): cmax_str = piece.to_cmax_str() if cmax_str: lines.append(cmax_str) # power and ground pins if self._show_pwr_gnd_pins: lines.append('+10: (61,20)') lines.append('gnd: (61,19)') return '\n'.join(lines) def _save_as_cmax_file(self): """ Presents a dialog box that will save the proto board we are visualizing as a CMax file. """ file_name = asksaveasfilename(title='Save as CMax file ...', filetypes=[('CMax files', CMAX_FILE_EXTENSION)]) if file_name and not file_name.endswith(CMAX_FILE_EXTENSION): file_name += CMAX_FILE_EXTENSION if file_name: save_file = open(file_name, 'w') save_file.write(self._to_cmax_str()) save_file.close() def _setup_menu(self): """ Sets up a menu that lets the user save the proto board we are visualizing as a CMax file. """ menu = Menu(self._parent, tearoff=0) save_menu = Menu(menu, tearoff=0) save_menu.add_command(label='Save as CMax file', command=self._save_as_cmax_file) menu.add_cascade(label='File', menu=save_menu) edit_menu = Menu(menu, tearoff=0) edit_menu.add_command(label='Redraw wires', command=self._redraw_wires) menu.add_cascade(label='Edit', menu=edit_menu) self._parent.config(menu=menu) def outline_piece_from_label(self, label): """ Draws the appropriate outline for the circuit piece with the given |label|. """ # try block in case canvas is gone with someone still calling this method try: self._canvas.delete(self._piece_outline_id) for piece in self._proto_board.get_pieces(): if label in piece.label.split(','): self._piece_outline_id = piece.outline_label( self._canvas, self._rc_to_xy(piece.top_left_loc), label) return except: pass def outline_wires_from_label(self, label): """ Draws outlines on the wires that have the given |label|. """ # try block in case canvas is gone with someone still calling this method try: for part in self._wire_outline_ids: self._canvas.delete(part) del self._wire_parts[part] self._wire_outline_ids = [] for wire in self._proto_board.get_wires(): if wire.node == label: x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) self._wire_outline_ids.extend([ self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='black', width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='cyan', width=3, capstyle='round') ]) for part in self._wire_outline_ids: self._wire_parts[part] = label except: pass def set_piece_highlight(self, f): """ Resets the piece highlighting function to |f|. """ self._piece_highlight = f def set_wire_highlight(self, f): """ Resets the wire highlighing function to |f|. """ def g(label): f(label) self.outline_wires_from_label(label) self._wire_highlight = g
class TkFlameGraphRenderer: """Renders a call forest to a flame graph using Tk graphics.""" def __init__(self, call_forest, tk, width, height, colours): self.call_forest = call_forest self.tk = tk self.width = width self.height = height self.colours = colours self.font = ('Courier', 12) self.character_width = 0.1 * Font(family='Courier', size=12).measure('mmmmmmmmmm') self.min_width_for_text = 3 * self.character_width self.min_height_for_text = 10 self.x_scale = float(width) / call_forest.samples() self.y_scale = float(height) / call_forest.depth() self.top = Toplevel(tk) self.top.title(call_forest.name) self.canvas = Canvas(self.top, width=width, height=height) self.canvas.pack() def render(self): x = 2.0 # ************************************ y = self.height - self.y_scale sorted_roots = sorted(self.call_forest.roots, key=lambda n: n.signature) for root in sorted_roots: self.render_call_tree(root, x, y) x += self.x_scale * root.samples # Not sure why I need this self.canvas.tag_raise('signature') def render_call_tree(self, root, x, y): self.render_call_tree_node(root, x, y) child_x = x child_y = y - self.y_scale sorted_children = sorted(root.children, key=lambda n: n.signature) for child in sorted_children: self.render_call_tree(child, child_x, child_y) child_x += self.x_scale * child.samples def render_call_tree_node(self, node, x, y): bbox = self.bounding_box(node, x, y) colour = self.colours.colour_for(node) id = self.canvas.create_rectangle(bbox, fill=colour) self.bind_events(id, node) self.text(node, x, y) def bounding_box(self, node, x, y): left = x right = left + self.x_scale * node.samples top = y bottom = y + self.y_scale bbox = (left, top, right, bottom) return bbox def text(self, node, x, y): height = self.y_scale width = self.x_scale * node.samples if height > self.min_height_for_text and width > self.min_width_for_text: sig = node.signature i = sig.rfind('.') method = sig[i:] char_width = int(width / self.character_width) chars = method[:char_width] if len( method) >= char_width else sig[-char_width:] # chars = node.signature[:char_width] id = self.canvas.create_text((x + width / 2, y + height / 2), text=chars, anchor='center', font=self.font, tags='signature') self.bind_events(id, node) def bind_events(self, id, node): self.canvas.tag_bind(id, '<Enter>', lambda e: self.mouse_enter(e, node)) self.canvas.tag_bind(id, '<Leave>', self.mouse_leave) self.canvas.tag_bind(id, '<Double-Button-1>', lambda e: self.zoom_in(e, node)) def mouse_enter(self, event, node): self.show_tooltip(self.canvas.canvasx(event.x), self.canvas.canvasy(event.y), node) def mouse_leave(self, event): self.hide_tooltip() def zoom_in(self, event, new_root): new_call_forest = CallForest(new_root.signature) new_call_forest.add_root(new_root) new_renderer = TkFlameGraphRenderer(new_call_forest, self.tk, self.width, self.height, self.colours) new_renderer.render() def show_tooltip(self, x, y, node): signature, samples = node.signature, node.samples percentage = (100.0 * samples) / self.call_forest.samples() text = '{} {} {:.2f}%'.format(signature, samples, percentage) c = self.canvas y_offset = (-10 if y > 30 else 20) anchor = 'sw' if x < self.width * 0.3 else 's' if x < self.width * 0.7 else 'se' label = c.create_text((x, y + y_offset), text=text, anchor=anchor, tags='tooltip', font=('Courier', 12)) bounds = c.bbox(label) c.create_rectangle(bounds, fill='white', width=0, tags='tooltip') c.tag_raise(label) pass def hide_tooltip(self): self.canvas.delete('tooltip')
class Proto_Board_Visualizer(Frame): """ Tk Frame to visualize Proto boards. """ def __init__(self, parent, proto_board, show_pwr_gnd_pins): """ |proto_board|: the proto board to visualize. |show_pwr_gnd_pins|: flag whether to show pwr and gnd pins as a reminder to connect to a power supply. """ Frame.__init__(self, parent, background=BACKGROUND_COLOR) self._parent = parent self._parent.title(WINDOW_TITLE) self._parent.resizable(0, 0) self._proto_board = proto_board self._show_pwr_gnd_pins = show_pwr_gnd_pins self._canvas = Canvas(self, width=WIDTH, height=HEIGHT, background=BACKGROUND_COLOR) self._tooltip_helper = Tooltip_Helper(self._canvas) self._wire_parts = {} # state for outline highlighing self._piece_outline_id = None self._wire_outline_ids = [] self._piece_highlight = lambda labels: None self._wire_highlight = lambda label: None self._setup_bindings() self._setup_menu() self._draw_proto_board() def _point_inside_piece(self, piece, x, y): """ Returns True if the point (|x|, |y|) is on the gien |piece|. """ r1, c1, r2, c2 = piece.bbox() x1, y1 = self._rc_to_xy((r1, c1)) x2, y2 = self._rc_to_xy((r2, c2)) return x1 <= x <= x2 + CONNECTOR_SIZE and y1 <= y <= y2 + CONNECTOR_SIZE def _maybe_show_tooltip(self, event): """ Shows a tooltip of the respective node if the cursor is on a wire or a valid location on the proto board, or the respective piece label if the cursor is on a piece. """ # check if cursor is on a wire item = self._canvas.find_closest(event.x, event.y)[0] if item in self._wire_parts: self._wire_highlight(self._wire_parts[item]) return # check if cursor is on a piece for piece in self._proto_board.get_pieces(): if self._point_inside_piece(piece, event.x, event.y): self._tooltip_helper.show_tooltip(event.x, event.y, 'ID: %s' % piece.label, background=TOOLTIP_DRAWABLE_LABEL_BACKGROUND) self._piece_highlight(piece.labels_at((event.x, event.y), self._rc_to_xy(piece.top_left_loc))) return self._piece_highlight([]) # check if cursor is on a valid proto board location loc = self._xy_to_rc(event.x, event.y) if loc: node = self._proto_board.node_for(loc) if node: self._wire_highlight(node) return self._wire_highlight(None) # if none of the above, remove previous tooltip, if any self._tooltip_helper.hide_tooltip() def _setup_bindings(self): """ Sets up event bindings. """ self._canvas.bind('<Motion>', self._maybe_show_tooltip) def _rc_to_xy(self, loc): """ Returns the top left corner of the connector located at row |r| column |c|. """ r, c = loc x = c * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING y = r * (CONNECTOR_SIZE + CONNECTOR_SPACING) + PADDING + ( num_vertical_separators(r) * (VERTICAL_SEPARATION - CONNECTOR_SPACING)) return x, y def _xy_to_rc(self, x, y): """ Returns the row and column of the valid location on the proto board containing the point (|x|, |y|), or None if no such location exists. """ for r in xrange(PROTO_BOARD_HEIGHT): for c in xrange(PROTO_BOARD_WIDTH): if valid_loc((r, c)): x1, y1 = self._rc_to_xy((r, c)) x2, y2 = x1 + CONNECTOR_SIZE, y1 + CONNECTOR_SIZE if x1 <= x <= x2 and y1 <= y <= y2: return r, c return None def _draw_connector(self, x, y, fill=CONNECTOR_COLOR, outline=CONNECTOR_COLOR): """ Draws a connector at the given coordinate and with the given colors. """ self._canvas.create_rectangle(x, y, x + CONNECTOR_SIZE, y + CONNECTOR_SIZE, fill=fill, outline=outline) def _draw_connectors(self): """ Draws the connectors on the proto board. """ for r in ROWS: for c in COLUMNS: if valid_loc((r, c)): self._draw_connector(*self._rc_to_xy((r, c))) def _draw_bus_line(self, y, color): """ Draws a bus line at the given horizontal position |y| and with the given |color|. """ x_1 = self._rc_to_xy((0, 1))[0] x_2 = self._rc_to_xy((0, PROTO_BOARD_WIDTH - 1))[0] self._canvas.create_line(x_1, y, x_2, y, fill=color) def _draw_bus_lines(self): """ Draws all four bus lines on the proto board. """ offset = 10 self._draw_bus_line(self._rc_to_xy((0, 0))[1] - offset, NEGATIVE_COLOR) self._draw_bus_line(self._rc_to_xy((1, 0))[1] + CONNECTOR_SIZE + offset, POSITIVE_COLOR) self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 2, 0))[1] - ( offset), NEGATIVE_COLOR) self._draw_bus_line(self._rc_to_xy((PROTO_BOARD_HEIGHT - 1, 0))[1] + ( CONNECTOR_SIZE + offset), POSITIVE_COLOR) def _draw_labels(self): """ Draws the row and column labels. """ # row labels row_labels = dict(zip(range(11, 1, -1), ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'])) for r in filter(lambda r: r in row_labels, ROWS): self._canvas.create_text(self._rc_to_xy((r, -1)), text=row_labels[r]) x, y = self._rc_to_xy((r, PROTO_BOARD_WIDTH)) self._canvas.create_text(x + CONNECTOR_SIZE, y, text=row_labels[r]) # columns labels h_offset = 2 v_offset = 10 for c in filter(lambda c: c == 0 or (c + 1) % 5 == 0, COLUMNS): x_1, y_1 = self._rc_to_xy((2, c)) self._canvas.create_text(x_1 + h_offset, y_1 - v_offset, text=(c + 1)) x_2, y_2 = self._rc_to_xy((PROTO_BOARD_HEIGHT - 3, c)) self._canvas.create_text(x_2 + h_offset, y_2 + CONNECTOR_SIZE + v_offset, text=(c + 1)) def _draw_gnd_pwr_pins(self): """ Draws pins to show the ground and power rails. """ # pin positions g_x, g_y = self._rc_to_xy((GROUND_RAIL, PROTO_BOARD_WIDTH - 3)) p_x, p_y = self._rc_to_xy((POWER_RAIL, PROTO_BOARD_WIDTH - 3)) # big rectangles large_h_offset = 3 * CONNECTOR_SIZE + 2 * CONNECTOR_SPACING small_h_offset = 2 large_v_offset = 7 small_v_offset = 3 self._canvas.create_rectangle(g_x - small_h_offset, g_y - large_v_offset, g_x + large_h_offset, g_y + CONNECTOR_SIZE + small_v_offset, fill=NEGATIVE_COLOR) self._canvas.create_rectangle(p_x - small_h_offset, p_y - small_v_offset, p_x + large_h_offset, p_y + CONNECTOR_SIZE + large_v_offset, fill=POSITIVE_COLOR) # connectors self._draw_connector(g_x, g_y, outline='black') self._draw_connector(p_x, p_y, outline='black') # text text_v_offset = 2 text_h_offset = 17 self._canvas.create_text(g_x + text_h_offset, g_y - text_v_offset, text='gnd', fill='white') self._canvas.create_text(p_x + text_h_offset, p_y + text_v_offset, text='+10', fill='white') def _draw_piece(self, piece): """ Draws the given circuit |piece| on the canvas. """ piece.draw_on(self._canvas, self._rc_to_xy(piece.top_left_loc)) def _draw_pieces(self): """ Draws all the pieces on the proto board. """ for piece in self._proto_board.get_pieces(): self._draw_piece(piece) def _draw_wire(self, wire): """ Draws the given |wire| on the canvas. """ x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) length = int(ceil(wire.length())) fill = 'white' if 1 < length < 10: fill = WIRE_COLORS[length] elif 10 <= length < 50: fill = WIRE_COLORS[(length + 9) / 10] def draw_wire(parts=None): if parts: for part in parts: self._canvas.delete(part) del self._wire_parts[part] new_parts = [self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=WIRE_OUTLINE, width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill=fill, width=3, capstyle='round')] for part in new_parts: self._canvas.tag_bind(part, '<Button-1>', lambda event: draw_wire( new_parts)) self._wire_parts[part] = wire.node draw_wire() def _draw_wires(self): """ Draws all the wires on the proto board. """ for wire in sorted(self._proto_board.get_wires(), key=lambda wire: -wire.length()): self._draw_wire(wire) def _redraw_wires(self): """ Erases then redraws the wires on the protoboard. """ for part in self._wire_parts: self._canvas.delete(part) self._wire_parts.clear() self._draw_wires() def _draw_proto_board(self): """ Draws the given |proto_board|. """ self._draw_connectors() self._draw_bus_lines() self._draw_labels() self._draw_pieces() self._draw_wires() if self._show_pwr_gnd_pins: self._draw_gnd_pwr_pins() self._canvas.pack() self.pack() def _wire_to_cmax_str(self, wire): """ Returns a CMax representation (when saved in a file) of the given |wire|. """ c1, r1 = loc_to_cmax_rep(wire.loc_1) c2, r2 = loc_to_cmax_rep(wire.loc_2) return 'wire: (%d,%d)--(%d,%d)' % (c1, r1, c2, r2) def _to_cmax_str(self): """ Returns a string CMax representation of the proto board we are visualizing. """ # header lines = ['#CMax circuit'] # wires for wire in self._proto_board.get_wires(): lines.append(self._wire_to_cmax_str(wire)) # circuit pieces for piece in self._proto_board.get_pieces(): cmax_str = piece.to_cmax_str() if cmax_str: lines.append(cmax_str) # power and ground pins if self._show_pwr_gnd_pins: lines.append('+10: (61,20)') lines.append('gnd: (61,19)') return '\n'.join(lines) def _save_as_cmax_file(self): """ Presents a dialog box that will save the proto board we are visualizing as a CMax file. """ file_name = asksaveasfilename(title='Save as CMax file ...', filetypes=[('CMax files', CMAX_FILE_EXTENSION)]) if file_name and not file_name.endswith(CMAX_FILE_EXTENSION): file_name += CMAX_FILE_EXTENSION if file_name: save_file = open(file_name, 'w') save_file.write(self._to_cmax_str()) save_file.close() def _setup_menu(self): """ Sets up a menu that lets the user save the proto board we are visualizing as a CMax file. """ menu = Menu(self._parent, tearoff=0) save_menu = Menu(menu, tearoff=0) save_menu.add_command(label='Save as CMax file', command=self._save_as_cmax_file) menu.add_cascade(label='File', menu=save_menu) edit_menu = Menu(menu, tearoff=0) edit_menu.add_command(label='Redraw wires', command=self._redraw_wires) menu.add_cascade(label='Edit', menu=edit_menu) self._parent.config(menu=menu) def outline_piece_from_label(self, label): """ Draws the appropriate outline for the circuit piece with the given |label|. """ # try block in case canvas is gone with someone still calling this method try: self._canvas.delete(self._piece_outline_id) for piece in self._proto_board.get_pieces(): if label in piece.label.split(','): self._piece_outline_id = piece.outline_label(self._canvas, self._rc_to_xy(piece.top_left_loc), label) return except: pass def outline_wires_from_label(self, label): """ Draws outlines on the wires that have the given |label|. """ # try block in case canvas is gone with someone still calling this method try: for part in self._wire_outline_ids: self._canvas.delete(part) del self._wire_parts[part] self._wire_outline_ids = [] for wire in self._proto_board.get_wires(): if wire.node == label: x_1, y_1 = self._rc_to_xy(wire.loc_1) x_2, y_2 = self._rc_to_xy(wire.loc_2) self._wire_outline_ids.extend([self._canvas.create_line( x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='black', width=7, capstyle='round'), self._canvas.create_line(x_1 + CONNECTOR_SIZE / 2, y_1 + CONNECTOR_SIZE / 2, x_2 + CONNECTOR_SIZE / 2, y_2 + CONNECTOR_SIZE / 2, fill='cyan', width=3, capstyle='round')]) for part in self._wire_outline_ids: self._wire_parts[part] = label except: pass def set_piece_highlight(self, f): """ Resets the piece highlighting function to |f|. """ self._piece_highlight = f def set_wire_highlight(self, f): """ Resets the wire highlighing function to |f|. """ def g(label): f(label) self.outline_wires_from_label(label) self._wire_highlight = g
class UI(): """The UI class manages the buttons, map and tiles. The latter two are visual depictions of the world and cells respectively """ def __init__(self, master, app, frame): """Create the window.""" self.master = master self.app = app self.frame = frame self.world = self.app.simulation.world self.add_buttons() self.add_other_widgets() self.add_map() log(">> Creating tiles") self.create_tiles() log(">> Painting tiles") self.paint_tiles() def add_buttons(self): """Add buttons to the frame.""" ground_button = Button(self.frame, text="TERRA", fg="red", command=self.draw_terrain) ground_button.grid(row=1, column=0, sticky=W + E) water_button = Button(self.frame, text="WATER", fg="red", command=self.toggle_water) water_button.grid(row=1, column=1, sticky=W + E) heat_button = Button(self.frame, text="HEAT", fg="red", command=self.draw_heat) heat_button.grid(row=1, column=2, sticky=W + E) def add_other_widgets(self): """Add other widgets to the frame.""" self.time_rate = StringVar() self.rate_label = Label(self.frame, textvariable=self.time_rate) self.rate_label.grid(row=0, column=0, sticky=W, columnspan=2) self.time_stamp = StringVar() self.time_label = Label(self.frame, textvariable=self.time_stamp) self.time_label.grid(row=0, column=2, columnspan=8) self.update_time_label(0) def add_map(self): """Add a blank map.""" self.map = Canvas(self.master, width=settings.map_width + 2 * settings.map_border, height=settings.map_height + 2 * settings.map_border, bg="black", highlightthickness=0) self.map.grid(columnspan=12) for c in range(12): self.frame.grid_columnconfigure(c, minsize=settings.map_width / 12) def create_tiles(self): """Create blank tiles.""" self.map.delete("all") self.tiles = [] for cell in self.world.cells: n_in_row = len( [c for c in self.world.cells if c.latitude == cell.latitude]) x_start = ((settings.map_width / 2.0) - (n_in_row / 2.0) * settings.tile_width) y_start = ((cell.latitude / settings.cell_degree_width) * settings.tile_height) self.tiles.append( self.map.create_rectangle(( x_start + (cell.longitude / 360.0) * n_in_row * settings.tile_width), y_start, (x_start + settings.tile_width + 1 + (cell.longitude / 360.0) * n_in_row * settings.tile_width), y_start + settings.tile_height, fill="yellow", outline="")) for x in range(len(self.tiles)): self.map.tag_bind(self.tiles[x], "<ButtonPress-1>", lambda event, arg=x: self.left_click_tile(arg)) self.map.tag_bind(self.tiles[x], "<ButtonPress-2>", lambda event, arg=x: self.right_click_tile(arg)) def left_click_tile(self, x): """Tell world to raise terrain at cell x.""" self.world.raise_cell(x, 1000) self.paint_tiles() def right_click_tile(self, x): """Tell world to raise terrain at cell x.""" self.world.raise_cell(x, -1000) self.paint_tiles() def paint_tiles(self): """Color the tiles.""" for x in range(len(self.tiles)): self.map.itemconfigure(self.tiles[x], fill=self.cell_color(self.world.cells[x])) def update_time_label(self, time): """Update the UI time label.""" year = time / (60 * 60 * 24 * 365) time -= year * (60 * 60 * 24 * 365) day = time / (60 * 60 * 24) time -= day * (60 * 60 * 24) hour = time / (60 * 60) time -= hour * (60 * 60) minute = time / 60 time -= minute * 60 second = time year = format(year, ",d") hour = "%02d" % (hour, ) minute = "%02d" % (minute, ) second = "%02d" % (second, ) self.time_stamp.set("{}:{}:{}, day {}, year {}.".format( hour, minute, second, day, year)) self.time_rate.set("x{}".format(settings.time_step_description)) def cell_color(self, cell): """Work out what color a tile should be. The color depends on the cell and the draw_mode parameter. """ if settings.draw_mode == "terrain": if cell.water.depth == 0.0 or settings.draw_water is False: col_min = [50, 20, 4] col_max = [255, 255, 255] p = ((cell.land.height - settings.min_ground_height) / (settings.max_ground_height - settings.min_ground_height)) else: col_min = [153, 204, 255] col_max = [20, 20, 80] p = cell.water.depth / 6000.0 if p > 1: p = 1 elif settings.draw_mode == "heat": if settings.draw_water is True: temp = cell.surface_temperature else: temp = cell.land.temperature if temp < 223: col_min = [0, 0, 0] col_max = [82, 219, 255] p = max(min((temp) / 223.0, 1), 0) elif temp < 273: col_min = [82, 219, 255] col_max = [255, 255, 255] p = max(min((temp - 223.0) / 50.0, 1), 0) elif temp < 313: col_min = [255, 255, 255] col_max = [255, 66, 0] p = max(min((temp - 273.0) / 40.0, 1), 0) else: col_min = [255, 66, 0] col_max = [0, 0, 0] p = max(min((temp - 313.0) / 100.0, 1), 0) elif settings.draw_mode == "wind": col_min = [0, 0, 0] col_max = [255, 255, 255] p = min(cell.wind_speed, 10) / 10 q = 1 - p col = [ int(q * col_min[0] + p * col_max[0]), int(q * col_min[1] + p * col_max[1]), int(q * col_min[2] + p * col_max[2]) ] return '#%02X%02X%02X' % (col[0], col[1], col[2]) def draw_terrain(self): """Paint map by altitude.""" settings.draw_mode = "terrain" self.paint_tiles() def draw_heat(self): """Paint map by land temperature.""" settings.draw_mode = "heat" self.paint_tiles() def toggle_water(self): """Toggle whether water is shown in terrain mode.""" settings.draw_water = not settings.draw_water self.paint_tiles()
class MainCanvas(object): """ The shapefile displaying device based on TKinter Canvas Attributes ---------- shapes : array The spatial units bbox : array The bounding box: minX, minY, maxX, maxY shp_type : integer The shape types: SHP_TYPE_POINT,SHP_TYPE_LINE,SHP_TYPE_POLYGON root : Tk The Tk Object attributeName : string The attribute name datalist : array The attribute data """ def __init__(self, shapes, bbox, shp_type, root, attributeName, datalist): self.shapes = shapes self.bbox = bbox self.shp_type = shp_type self.root = root self.attributeName = attributeName self.datalist = datalist self.__createCanvas() def __createCanvas(self): """ Create the canvas and draw all the spatial objects """ self.canvasRoot = Toplevel() self.canvasRoot.title(self.attributeName) self.canvasRoot.lower(belowThis=self.root) self.mainCanvas = Canvas(self.canvasRoot, bg='black', width=canvasWidth + margin_x, height=canvasHeight + margin_y, scrollregion=('-50c', '-50c', "50c", "50c")) #Change by Sagar for Full Screen self.canvasRoot.state('zoomed') self.canvasRoot.geometry = ("1000x900+0+0") #Change End self.__drawShape() self.mainCanvas.pack() def __drawShape(self): """ Draw all the spatial objects on the canvas """ minX, minY, maxX, maxY = self.bbox[0], self.bbox[1], self.bbox[ 2], self.bbox[3] # calculate ratios of visualization ratiox = canvasWidth / (maxX - minX) ratioy = canvasHeight / (maxY - minY) # take the smaller ratio of window size to geographic distance ratio = ratiox if ratio > ratioy: ratio = ratioy if self.shp_type == SHP_TYPE_POINT: self.__drawPoints(minX, minY, maxX, maxY, ratio) elif self.shp_type == SHP_TYPE_LINE: self.__drawPolylines(minX, minY, maxX, maxY, ratio) elif self.shp_type == SHP_TYPE_POLYGON: self.__drawPolygons(minX, minY, maxX, maxY, ratio) def __drawPoints(self, minX, minY, maxX, maxY, ratio): """ Draw points on the canvas """ tag_count = 0 # loop through each point for point in self.shapes: #define an empty xylist for holding converted coordinates x = int((point.x - minX) * ratio) + margin_x / 2 y = int((maxY - point.y) * ratio) + margin_y / 2 _point = self.mainCanvas.create_oval(x - 2, y - 2, x + 2, y + 2, outline=point.color, fill=point.color, width=2, tags=self.datalist[tag_count]) self.mainCanvas.tag_bind(_point, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __drawPolylines(self, minX, minY, maxX, maxY, ratio): """ Draw polylines on the canvas """ tag_count = 0 # loop through each polyline for polyline in self.shapes: #define an empty xylist for holding converted coordinates xylist = [] # loops through each point and calculate the window coordinates, put in xylist for j in range(len(polyline.x)): pointx = int((polyline.x[j] - minX) * ratio) + margin_x / 2 pointy = int((maxY - polyline.y[j]) * ratio) + margin_y / 2 xylist.append(pointx) xylist.append(pointy) # loop through each part of the polyline for k in range(polyline.partsNum): #get the end sequence number of points in the part if (k == polyline.partsNum - 1): endPointIndex = len(polyline.x) else: endPointIndex = polyline.partsIndex[k + 1] # define a temporary list for holding the part coordinates tempXYlist = [] #take out points' coordinates for the part and add to the temporary list for m in range(polyline.partsIndex[k], endPointIndex): tempXYlist.append(xylist[m * 2]) tempXYlist.append(xylist[m * 2 + 1]) # create the line _line = self.mainCanvas.create_line( tempXYlist, fill=polyline.color, tags=self.datalist[tag_count]) self.mainCanvas.tag_bind(_line, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __drawPolygons(self, minX, minY, maxX, maxY, ratio): """ Draw polygons on the canvas """ tag_count = 0 for polygon in self.shapes: #define an empty xylist for holding converted coordinates xylist = [] # loops through each point and calculate the window coordinates, put in xylist for point in polygon.points: pointx = int((point.x - minX) * ratio) + +margin_x / 0.5 pointy = int((maxY - point.y) * ratio) + +margin_y / 5 xylist.append(pointx) xylist.append(pointy) ## print xylist """ polyline.partsIndex is a tuple data type holding the starting points for each part. For example, if the polyline.partsIndex of a polyline equals to (0, 4, 9), and the total points, which is calcuate by len(polyline.points) equals to 13. This means that the polyline has three parts, and the each part would have the points as follows. part 1: p0,p1,p2,p3 part 2: p4,p5,p6,p7,p8 part 3: p9,p10,p11,p12 The xylist would be: xylist = [x0, y0, x1, y1, x2, y2, x3, y3, x4, y4....x12, y12] where xylist[0] = x0 xylist[1] = y0 xylist[2] = x1 xylist[3] = y1 ..... To draw the first part of polyline, we want to get tempXYlist as tempXYlist = [x0, y0, x1, y1, x2, y2, x3, y3] At this time, m is in range(0,4) xylist[m*2] would be is x0(when m=0), x1(when m=1), x2(when m=2), x3(when m=3) xylist[m*2+1] would be is y0(when m=0), y1(when m=1), y2(when m=2), y3(when m=3) """ for k in range(polygon.partsNum): #get the end sequence number of points in the part if (k == polygon.partsNum - 1): endPointIndex = len(polygon.points) else: endPointIndex = polygon.partsIndex[k + 1] #Define a temporary list for holding the part coordinates tempXYlist = [] tempXlist = [] tempYlist = [] #take out points' coordinates for the part and add to the temporary list for m in range(polygon.partsIndex[k], endPointIndex): tempXYlist.append(xylist[m * 2]) tempXYlist.append(xylist[m * 2 + 1]) tempXlist.append(xylist[m * 2]) tempYlist.append(xylist[m * 2 + 1]) xMax = max(tempXlist) xMin = min(tempXlist) yMax = max(tempYlist) yMin = min(tempYlist) if xMax == xMin: xMin = xMax - 1 if yMax == yMin: yMin = yMax - 1 tempVar = False #while not tempVar: xPoint = rd.randrange(xMin, xMax) yPoint = rd.randrange(yMin, yMax) tempVar = point_inside_polygon(xPoint, yPoint, tempXYlist) startIndex = polygon.partsIndex[ k] #start index for our positive polygon. tempPoints = polygon.points[ startIndex: endPointIndex] #we get our temppoints to help use create our polygon using positive data newPolygon = Polygon( tempPoints ) #here we create our polygons using positve data area = newPolygon.getArea() # Calculate the area #Sagar Jha center added to calculate centroid of polygon center = newPolygon.getCentroid() xCenter = int((center.x - minX) * ratio) + +margin_x / 0.5 yCenter = int((maxY - center.y) * ratio) + +margin_y / 5 if area > 0: _polygon = self.mainCanvas.create_polygon( tempXYlist, activefill="blue", fill=polygon.color, outline="blue", tags=self.datalist[tag_count] ) #creating our polygon outline #print k,_polygon #Michigan Special Condition according to its 2 parts if tag_count == 48: if k == 4: _oval = self.mainCanvas.create_oval(xCenter, yCenter, xCenter + 5, yCenter + 5, outline="red", fill="green", width=2, tags=center) dict1[_oval] = [center.x, center.y] else: if k == 0: #print "Tag Count: ",tag_count," ",self.mainCanvas.gettags(_polygon)[0] _oval = self.mainCanvas.create_oval(xCenter, yCenter, xCenter + 5, yCenter + 5, outline="red", fill="green", width=2, tags=center) dict1[_oval] = [center.x, center.y] #_oval1 = self.mainCanvas.create_oval(xPoint, yPoint,xPoint +5,yPoint+ 5, outline="red",fill="green", width=2) else: # If it is a hole, fill with the same color as the canvas background color _polygon = self.mainCanvas.create_polygon( tempXYlist, fill="black", outline="black", tags=self.datalist[tag_count]) #self.mainCanvas.tag_bind( _polygon, '<ButtonPress-1>', self.__showAttriInfo) #self.mainCanvas.tag_bind( _oval, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __showAttriInfo(self, event): """ Show attribute information of clicked unit """ widget_id = event.widget.find_closest(event.x, event.y) if widget_id[0] in dict1.keys(): print widget_id[0], dict1[widget_id[0]][0], dict1[widget_id[0]][1] else: print "click!!!!", widget_id print self.attributeName + " is: " + self.mainCanvas.gettags( widget_id)[0]
class UI(): """The UI class manages the buttons, map and tiles. The latter two are visual depictions of the world and cells respectively """ def __init__(self, master, app, frame): """Create the window.""" self.master = master self.app = app self.frame = frame self.world = self.app.simulation.world self.add_buttons() self.add_other_widgets() self.add_map() log(">> Creating tiles") self.create_tiles() log(">> Painting tiles") self.paint_tiles() def add_buttons(self): """Add buttons to the frame.""" ground_button = Button(self.frame, text="TERRA", fg="red", command=self.draw_terrain) ground_button.grid(row=1, column=0, sticky=W+E) water_button = Button(self.frame, text="WATER", fg="red", command=self.toggle_water) water_button.grid(row=1, column=1, sticky=W+E) heat_button = Button(self.frame, text="HEAT", fg="red", command=self.draw_heat) heat_button.grid(row=1, column=2, sticky=W+E) def add_other_widgets(self): """Add other widgets to the frame.""" self.time_rate = StringVar() self.rate_label = Label(self.frame, textvariable=self.time_rate) self.rate_label.grid(row=0, column=0, sticky=W, columnspan=2) self.time_stamp = StringVar() self.time_label = Label(self.frame, textvariable=self.time_stamp) self.time_label.grid(row=0, column=2, columnspan=8) self.update_time_label(0) def add_map(self): """Add a blank map.""" self.map = Canvas(self.master, width=settings.map_width + 2*settings.map_border, height=settings.map_height + 2*settings.map_border, bg="black", highlightthickness=0) self.map.grid(columnspan=12) for c in range(12): self.frame.grid_columnconfigure(c, minsize=settings.map_width/12) def create_tiles(self): """Create blank tiles.""" self.map.delete("all") self.tiles = [] for cell in self.world.cells: n_in_row = len([c for c in self.world.cells if c.latitude == cell.latitude]) x_start = ((settings.map_width/2.0) - (n_in_row/2.0)*settings.tile_width) y_start = ((cell.latitude/settings.cell_degree_width) * settings.tile_height) self.tiles.append(self.map.create_rectangle( (x_start + (cell.longitude/360.0) * n_in_row * settings.tile_width), y_start, (x_start + settings.tile_width + 1 + (cell.longitude/360.0) * n_in_row * settings.tile_width), y_start + settings.tile_height, fill="yellow", outline="")) for x in range(len(self.tiles)): self.map.tag_bind(self.tiles[x], "<ButtonPress-1>", lambda event, arg=x: self.left_click_tile(arg)) self.map.tag_bind(self.tiles[x], "<ButtonPress-2>", lambda event, arg=x: self.right_click_tile(arg)) def left_click_tile(self, x): """Tell world to raise terrain at cell x.""" self.world.raise_cell(x, 1000) self.paint_tiles() def right_click_tile(self, x): """Tell world to raise terrain at cell x.""" self.world.raise_cell(x, -1000) self.paint_tiles() def paint_tiles(self): """Color the tiles.""" for x in range(len(self.tiles)): self.map.itemconfigure(self.tiles[x], fill=self.cell_color(self.world.cells[x])) def update_time_label(self, time): """Update the UI time label.""" year = time / (60*60*24*365) time -= year*(60*60*24*365) day = time / (60*60*24) time -= day * (60*60*24) hour = time / (60*60) time -= hour*(60*60) minute = time / 60 time -= minute*60 second = time year = format(year, ",d") hour = "%02d" % (hour, ) minute = "%02d" % (minute, ) second = "%02d" % (second, ) self.time_stamp.set("{}:{}:{}, day {}, year {}." .format(hour, minute, second, day, year)) self.time_rate.set("x{}".format(settings.time_step_description)) def cell_color(self, cell): """Work out what color a tile should be. The color depends on the cell and the draw_mode parameter. """ if settings.draw_mode == "terrain": if cell.water.depth == 0.0 or settings.draw_water is False: col_min = [50, 20, 4] col_max = [255, 255, 255] p = ((cell.land.height - settings.min_ground_height) / (settings.max_ground_height - settings.min_ground_height)) else: col_min = [153, 204, 255] col_max = [20, 20, 80] p = cell.water.depth/6000.0 if p > 1: p = 1 elif settings.draw_mode == "heat": if settings.draw_water is True: temp = cell.surface_temperature else: temp = cell.land.temperature if temp < 223: col_min = [0, 0, 0] col_max = [82, 219, 255] p = max(min((temp)/223.0, 1), 0) elif temp < 273: col_min = [82, 219, 255] col_max = [255, 255, 255] p = max(min((temp-223.0)/50.0, 1), 0) elif temp < 313: col_min = [255, 255, 255] col_max = [255, 66, 0] p = max(min((temp-273.0)/40.0, 1), 0) else: col_min = [255, 66, 0] col_max = [0, 0, 0] p = max(min((temp-313.0)/100.0, 1), 0) elif settings.draw_mode == "wind": col_min = [0, 0, 0] col_max = [255, 255, 255] p = min(cell.wind_speed, 10)/10 q = 1-p col = [int(q*col_min[0] + p*col_max[0]), int(q*col_min[1] + p*col_max[1]), int(q*col_min[2] + p*col_max[2])] return '#%02X%02X%02X' % (col[0], col[1], col[2]) def draw_terrain(self): """Paint map by altitude.""" settings.draw_mode = "terrain" self.paint_tiles() def draw_heat(self): """Paint map by land temperature.""" settings.draw_mode = "heat" self.paint_tiles() def toggle_water(self): """Toggle whether water is shown in terrain mode.""" settings.draw_water = not settings.draw_water self.paint_tiles()
class MainCanvas(object): """ The shapefile displaying device based on TKinter Canvas Attributes ---------- shapes : array The spatial units bbox : array The bounding box: minX, minY, maxX, maxY shp_type : integer The shape types: SHP_TYPE_POINT,SHP_TYPE_LINE,SHP_TYPE_POLYGON root : Tk The Tk Object attributeName : string The attribute name datalist : array The attribute data """ def __init__(self,shapes,bbox,shp_type,root,attributeName,datalist): self.shapes = shapes self.bbox = bbox self.shp_type = shp_type self.root = root self.attributeName = attributeName self.datalist = datalist self.__createCanvas() def __createCanvas(self): """ Create the canvas and draw all the spatial objects """ self.canvasRoot = Toplevel() self.canvasRoot.title(self.attributeName) self.canvasRoot.lower(belowThis = self.root) self.mainCanvas = Canvas(self.canvasRoot, bg = 'black', width = canvasWidth+margin_x, height = canvasHeight+margin_y, scrollregion=('-50c','-50c',"50c","50c")) #Change by Sagar for Full Screen self.canvasRoot.state('zoomed') self.canvasRoot.geometry=("1000x900+0+0") #Change End self.__drawShape() self.mainCanvas.pack() def __drawShape(self): """ Draw all the spatial objects on the canvas """ minX, minY, maxX, maxY = self.bbox[0],self.bbox[1],self.bbox[2],self.bbox[3] # calculate ratios of visualization ratiox = canvasWidth/(maxX-minX) ratioy = canvasHeight/(maxY-minY) # take the smaller ratio of window size to geographic distance ratio = ratiox if ratio>ratioy: ratio = ratioy if self.shp_type == SHP_TYPE_POINT: self.__drawPoints(minX, minY, maxX, maxY, ratio) elif self.shp_type == SHP_TYPE_LINE: self.__drawPolylines(minX, minY, maxX, maxY, ratio) elif self.shp_type == SHP_TYPE_POLYGON: self.__drawPolygons(minX, minY, maxX, maxY, ratio) def __drawPoints(self,minX, minY, maxX, maxY,ratio): """ Draw points on the canvas """ tag_count = 0 # loop through each point for point in self.shapes: #define an empty xylist for holding converted coordinates x = int((point.x-minX)*ratio)+margin_x/2 y = int((maxY-point.y)*ratio)+margin_y/2 _point = self.mainCanvas.create_oval(x-2, y-2, x+2, y+2,outline=point.color, fill=point.color, width=2, tags = self.datalist[tag_count]) self.mainCanvas.tag_bind( _point, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __drawPolylines(self,minX, minY, maxX, maxY,ratio): """ Draw polylines on the canvas """ tag_count = 0 # loop through each polyline for polyline in self.shapes: #define an empty xylist for holding converted coordinates xylist = [] # loops through each point and calculate the window coordinates, put in xylist for j in range(len(polyline.x)): pointx = int((polyline.x[j]-minX)*ratio)+margin_x/2 pointy = int((maxY-polyline.y[j])*ratio)+margin_y/2 xylist.append(pointx) xylist.append(pointy) # loop through each part of the polyline for k in range(polyline.partsNum): #get the end sequence number of points in the part if (k==polyline.partsNum-1): endPointIndex = len(polyline.x) else: endPointIndex = polyline.partsIndex[k+1] # define a temporary list for holding the part coordinates tempXYlist = [] #take out points' coordinates for the part and add to the temporary list for m in range(polyline.partsIndex[k], endPointIndex): tempXYlist.append(xylist[m*2]) tempXYlist.append(xylist[m*2+1]) # create the line _line = self.mainCanvas.create_line(tempXYlist,fill=polyline.color, tags = self.datalist[tag_count]) self.mainCanvas.tag_bind( _line, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __drawPolygons(self,minX, minY, maxX, maxY,ratio): """ Draw polygons on the canvas """ tag_count = 0 for polygon in self.shapes: #define an empty xylist for holding converted coordinates xylist = [] # loops through each point and calculate the window coordinates, put in xylist for point in polygon.points: pointx = int((point.x -minX)*ratio) + +margin_x/0.5 pointy = int((maxY- point.y)*ratio) + +margin_y/5 xylist.append(pointx) xylist.append(pointy) ## print xylist """ polyline.partsIndex is a tuple data type holding the starting points for each part. For example, if the polyline.partsIndex of a polyline equals to (0, 4, 9), and the total points, which is calcuate by len(polyline.points) equals to 13. This means that the polyline has three parts, and the each part would have the points as follows. part 1: p0,p1,p2,p3 part 2: p4,p5,p6,p7,p8 part 3: p9,p10,p11,p12 The xylist would be: xylist = [x0, y0, x1, y1, x2, y2, x3, y3, x4, y4....x12, y12] where xylist[0] = x0 xylist[1] = y0 xylist[2] = x1 xylist[3] = y1 ..... To draw the first part of polyline, we want to get tempXYlist as tempXYlist = [x0, y0, x1, y1, x2, y2, x3, y3] At this time, m is in range(0,4) xylist[m*2] would be is x0(when m=0), x1(when m=1), x2(when m=2), x3(when m=3) xylist[m*2+1] would be is y0(when m=0), y1(when m=1), y2(when m=2), y3(when m=3) """ for k in range(polygon.partsNum): #get the end sequence number of points in the part if (k==polygon.partsNum-1): endPointIndex = len(polygon.points) else: endPointIndex = polygon.partsIndex[k+1] #Define a temporary list for holding the part coordinates tempXYlist = [] tempXlist = [] tempYlist = [] #take out points' coordinates for the part and add to the temporary list for m in range(polygon.partsIndex[k], endPointIndex): tempXYlist.append(xylist[m*2]) tempXYlist.append(xylist[m*2+1]) tempXlist.append (xylist[m*2]) tempYlist.append (xylist[m*2+1]) xMax = max(tempXlist) xMin = min(tempXlist) yMax = max(tempYlist) yMin = min(tempYlist) if xMax == xMin: xMin = xMax - 1 if yMax == yMin: yMin = yMax - 1 tempVar = False #while not tempVar: xPoint = rd.randrange(xMin,xMax) yPoint = rd.randrange(yMin,yMax) tempVar = point_inside_polygon(xPoint,yPoint,tempXYlist) startIndex = polygon.partsIndex[k] #start index for our positive polygon. tempPoints = polygon.points[startIndex: endPointIndex]#we get our temppoints to help use create our polygon using positive data newPolygon = Polygon(tempPoints) #here we create our polygons using positve data area = newPolygon.getArea() # Calculate the area #Sagar Jha center added to calculate centroid of polygon center = newPolygon.getCentroid() xCenter = int((center.x -minX)*ratio) + +margin_x/0.5 yCenter = int((maxY- center.y)*ratio) + +margin_y/5 if area > 0: _polygon = self.mainCanvas.create_polygon(tempXYlist,activefill="blue",fill=polygon.color,outline="blue",tags = self.datalist[tag_count])#creating our polygon outline #print k,_polygon #Michigan Special Condition according to its 2 parts if tag_count == 48: if k==4: _oval = self.mainCanvas.create_oval(xCenter, yCenter,xCenter +5,yCenter+ 5, outline="red",fill="green", width=2,tags = center) dict1[_oval]=[center.x,center.y] else: if k==0: #print "Tag Count: ",tag_count," ",self.mainCanvas.gettags(_polygon)[0] _oval = self.mainCanvas.create_oval(xCenter, yCenter,xCenter +5,yCenter+ 5, outline="red",fill="green", width=2,tags = center) dict1[_oval]=[center.x,center.y] #_oval1 = self.mainCanvas.create_oval(xPoint, yPoint,xPoint +5,yPoint+ 5, outline="red",fill="green", width=2) else: # If it is a hole, fill with the same color as the canvas background color _polygon = self.mainCanvas.create_polygon(tempXYlist,fill="black",outline="black", tags = self.datalist[tag_count]) #self.mainCanvas.tag_bind( _polygon, '<ButtonPress-1>', self.__showAttriInfo) #self.mainCanvas.tag_bind( _oval, '<ButtonPress-1>', self.__showAttriInfo) tag_count += 1 def __showAttriInfo(self,event): """ Show attribute information of clicked unit """ widget_id=event.widget.find_closest(event.x, event.y) if widget_id[0] in dict1.keys(): print widget_id[0], dict1[widget_id[0]][0],dict1[widget_id[0]][1] else: print "click!!!!", widget_id print self.attributeName+" is: "+self.mainCanvas.gettags(widget_id)[0]
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")