class PhraseEditor(HasTraits): """A graphical editor for musical phrases.""" phrase = Instance(Phrase) """The phrase which is edited.""" selected_point = Instance(Point) """The point that is currently moved around.""" use_grid = Bool """Whether added points shall be snapped to a grid.""" grid_resolution = Range(2,64, 16) """The resolution of the grid.""" point_handle_size = Int(4) """The size of the handles used to move points around.""" def __init__(self, toplevel, phrase, **kwargs): """ Initializes a PhraseEditor. @param toplevel: the Tk Toplevel window @param phrase: the phrase to be edited """ # The Tk Toplevel object to which the editor will be attached self.toplevel = toplevel control_frame = Frame(self.toplevel) control_frame.pack(side=TOP) title_frame = Frame(control_frame, padx=8) title_frame.pack(side=LEFT) Label(title_frame, text="Title").pack(side=LEFT) self.title_entry_manager = EntryManager( title_frame, self, 'phrase.name' ) self.title_entry_manager.entry.pack(side=LEFT) grid_frame = Frame(control_frame, padx=8) grid_frame.pack(side=LEFT) self.grid_checkbutton_manager = CheckbuttonManager( grid_frame, self, 'use_grid', text="Grid" ) self.grid_checkbutton_manager.checkbutton.pack(side=LEFT) Label(grid_frame, text="Resolution").pack(side=LEFT) self.grid_resolution_spinbox_manager = SpinboxManager( grid_frame, self, 'grid_resolution', ) self.grid_resolution_spinbox_manager.spinbox.pack(side=LEFT) steps_frame = Frame(control_frame, padx=8) steps_frame.pack(side=LEFT) Label(steps_frame, text="Steps").pack(side=LEFT) self.steps_spinbox_manager = SpinboxManager( steps_frame, self, 'phrase.steps', ) self.steps_spinbox_manager.spinbox.pack(side=LEFT) transpose_frame = Frame(control_frame, padx=8) transpose_frame.pack(side=LEFT) Label(transpose_frame, text="Transpose").pack(side=LEFT) self.transpose_spinbox_manager = SpinboxManager( transpose_frame, self, 'phrase.transpose', ) self.transpose_spinbox_manager.spinbox.pack(side=LEFT) self.v_point_type = StringVar() self.v_point_type.set('Note') OptionMenu( control_frame, self.v_point_type, 'Note', 'Rest', ).pack(side=LEFT) self.canvas = Canvas(self.toplevel, width=600, height=400) self.canvas.pack(side=BOTTOM) # Maps a Point to the ID of its handle, which is a rectangle on the # canvas. self._point_handles = {} # Maps a tuple of two Points to the ID of the line connecting them on # the canvas. self._point_lines = {} # Maps a Point to the line to its next Point that is currently played. self._playing_lines = {} # A set of dotted lines marking the grid on the canvas. self._grid_lines = set() super(PhraseEditor, self).__init__( phrase=phrase, use_grid=True, **kwargs ) def find_point(x,y): """ @return: the point at the specified position on the canvas. """ s = self.point_handle_size for point in self.phrase.points: px,py = point.pos if px-s <= x <= px+s and py-s <= y <= py+s: return point return None def snap_to_grid(x,y): """ Rounds the given coordinates to the grid defined by L{PhraseEditor.grid_resolution}. """ res = self.grid_resolution return round(x/float(res))*res, round(y/float(res))*res def button_pressed(event): """ When the left mouse button is pressed over a point, it becomes the L{PhraseEditor.selected_point}, which can be moved around by L{button_motion()}. If there is no point under the mouse pointer, a new point is appended to the L{Phrase}. """ x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) point = find_point(x,y) if point is None: if self.use_grid: x,y = snap_to_grid(x,y) point = Point( pos=(int(x),int(y)), type=self.v_point_type.get(), ) self.phrase.points.append(point) self.selected_point = point self.canvas.bind('<Button-1>', button_pressed) def button_motion(event): """ If the mouse is moved while the left or miffle mouse button is held down and a point is selected, this point is moved to the current mouse pointer position. """ if self.selected_point is None: return x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) if self.use_grid: x,y = snap_to_grid(x,y) canvas_config = self.canvas.config() canvas_width = int(canvas_config['width'][-1]) canvas_height = int(canvas_config['height'][-1]) self.selected_point.pos = ( min(canvas_width, max(0, int(x))), min(canvas_height, max(0, int(y))) ) self.canvas.bind('<B1-Motion>', button_motion) self.canvas.bind('<B2-Motion>', button_motion) def button_released(event): """ When releasing the left or middle mouse button, the currently selected point is deselected. """ self.selected_point = None self.canvas.bind('<ButtonRelease-1>', button_released) self.canvas.bind('<ButtonRelease-2>', button_released) def right_button_pressed(event): """ Pressing the right mouse button over a point removes it from the L{Phrase}. """ x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) point = find_point(x,y) if point is not None: self.phrase.points.remove(point) self.canvas.bind('<Button-3>', right_button_pressed) def middle_button_pressed(event): if self.selected_point is not None: return x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) point = find_point(x,y) if point is None: return new_point = Point(pos=point.pos, type=self.v_point_type.get()) self.phrase.points.insert( self.phrase.points.index(point)+1, new_point ) self.selected_point = new_point self.canvas.bind('<Button-2>', middle_button_pressed) def close(self): """ Closes the editor, destroying its Toplevel window. """ #del self.phrase #del self.selected_point #del self._point_handles #del self._point_lines #del self._playing_lines self.toplevel.destroy() def _add_point_handle(self, point): """ Adds a point handle for the given point to the canvas. """ s = self.point_handle_size px, py = point.pos self._point_handles[point] = self.canvas.create_rectangle( px-s,py-s, px+s,py+s, ) def _remove_point_handle(self, point): """ Removes the point handle for the given point. """ self.canvas.delete(self._point_handles[point]) del self._point_handles[point] def _add_point_line(self, point1, point2): """ Adds a line between two points on the canvas. """ px1, py1 = point1.pos px2, py2 = point2.pos self._point_lines[ (point1, point2) ] = self.canvas.create_line( px1,py1, px2,py2, dash=[2,2] if point2.type == 'Rest' else None ) def _remove_point_line(self, point1, point2): """ Removes the line between two given points. """ px1, py1 = point1.pos px2, py2 = point2.pos self.canvas.delete(self._point_lines[(point1, point2)]) del self._point_lines[(point1, point2)] def _remove_point_lines(self): for points, line in self._point_lines.iteritems(): self.canvas.delete(line) self._point_lines = {} def _remove_point_handles(self): for point, handle in self._point_handles.iteritems(): self.canvas.delete(handle) self._point_handles = {} @on_trait_change('phrase, phrase:points') def _points_changed(self, obj, name, old, new): if self.phrase is None: return self._remove_point_handles() self._remove_point_lines() points = self.phrase.points for i, point in enumerate(points): self._add_point_handle(point) if i > 0: self._add_point_line(points[i-1], point) @on_trait_change('phrase:points:pos') def _point_pos_changed(self, point, name, old, new): """ When a point's position changes, its handle and lines to its surrounding points are redefined. """ self._remove_point_handle(point) self._add_point_handle(point) points = self.phrase.points assert point in points i = points.index(point) if i > 0: source_point = points[i-1] self._remove_point_line(source_point, point) self._add_point_line(source_point, point) if i < len(points)-1: target_point = points[i+1] self._remove_point_line(point, target_point) self._add_point_line(point, target_point) @on_trait_change('phrase.name') def _phrase_name_changed(self): """ When the name of the phrase changes, the window title is update. """ if self.phrase is None: return self.toplevel.title("Phrase: %s" % self.phrase.name) def add_playing_point(self, point, color): if point in self._playing_lines: self.remove_playing_point(point, color) if point not in self.phrase.points: return px,py, px2,py2 = self.phrase.get_line(point) self._playing_lines[point] = self.canvas.create_line( px,py, px2,py2, fill=color, width=2.0, dash=[2,2] if point.type == 'Rest' else None ) def remove_playing_point(self, point, color): if point not in self._playing_lines: return self.canvas.delete(self._playing_lines[point]) del self._playing_lines[point] @on_trait_change('use_grid, grid_resolution') def _grid_changed(self): """ Draws a grid if L{PhraseEditor.use_grid} is True, otherwise removes it. """ for rect in self._grid_lines: self.canvas.delete(rect) self._grid_lines.clear() if self.use_grid: config = self.canvas.config() w = int(config['width'][-1]) h = int(config['height'][-1]) res = self.grid_resolution for y in range(0, h, res): self._grid_lines.add(self.canvas.create_line( 0,y, w,y, dash=[1,res-1], fill="#666666" ))
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 AppAnalysis: def __init__(self, root): self.canvas = Canvas(root, width = 400, height = 350) self.canvas.configure(cursor="crosshair") self.canvas.pack(expand=YES, fill=BOTH, side='right') self.canvas.bind("<Key>", self.handle_key) self.canvas.bind("<Double-Button-1>", self.set_focus) self.canvas.bind("<Button-1>", self.set_cursor) self.canvas.bind("<Return>", self.remove_highlight) self.image, self.ponto1, self.ponto2 = (None, None, None) self.menubar = Menu(root) filemenu = Menu(self.menubar, tearoff=0) filemenu.add_command(label="Open Image", command=self.openImage) filemenu.add_command(label="Save", command=self.hello) filemenu.add_separator() filemenu.add_command(label="Exit", command=root.quit) self.menubar.add_cascade(label="File", menu=filemenu) editmenu = Menu(self.menubar, tearoff=0) for e in ("Cut","Copy","Paste"): editmenu.add_command(label=e, command=self.hello) self.menubar.add_cascade(label="Edit", menu=editmenu) filtermenu = Menu(self.menubar, tearoff=0) filtermenu.add_command(label="Threshold", command=self.thresholdFilter) self.menubar.add_cascade(label="Filter", menu=filtermenu) reportmenu = Menu(self.menubar, tearoff=0) reportmenu.add_command(label="Relatorio.txt", command=self.generateReport) reportmenu.add_command(label="Relatorio.pdf") reportmenu.add_command(label="Email") self.menubar.add_cascade(label="Report", menu=reportmenu) helpmenu = Menu(self.menubar, tearoff=0) helpmenu.add_command(label="About", command=self.hello) self.menubar.add_cascade(label="Help", menu=helpmenu) root.config(menu=self.menubar) self.toolbar = Frame(root) self.toolbar.pack(side='left', fill='both') clean = Label(self.toolbar, text='Clean') clean.bind("<Button-1>", self.clean) b = Label(self.toolbar, text='B') c = Label(self.toolbar, text='C') d = Label(self.toolbar, text='D') for w in (clean,b,c,d): w.configure(relief="groove", font="Times 12 bold") w.pack(fill='both') def openImage(self): arquivo = tkFileDialog.askopenfile(parent=self.canvas,mode='rb', title='Imagem') e = ['GIF','JPEG','JPG','BMP','PNG','TIF'] if(e.__contains__(arquivo.name.split(".")[-1].upper())): self.ponto1, self.ponto2 = (None,None) img_tmp = Image.open(arquivo) #self.img_name = path.dirname(path.abspath(arquivo.name)) self.img_name = arquivo.name print self.img_name self.new_img_name = arquivo.name.split('/')[-1] + "_tmp.gif" pathtemp = mydir +"/temp/"+ self.new_img_name img_tmp.save(pathtemp) self.image = PhotoImage(file=pathtemp) self.setImage() self.canvas.bind("<Button-1>", self.click) self.proporcao = "" def clean(self, event): self.ponto1, self.ponto2 = (None,None) self.setImage() self.proporcao = "" def setImage(self): self.canvas.delete(ALL) if self.image.width() > 200 and self.image.height > 200: self.canvas.config(width = self.image.width()) self.canvas.config(height = self.image.height()) self.canvas.create_image(0, 0, image=self.image, anchor=NW) def generateReport(self): report = GeradorRelatorio(self.img_name) report.start() def hello(self): print "hello!" def thresholdFilter(self): img = Image.open(self.img_name) new_img = img.filter(ImageFilter.BLUR) aux = mydir +"/temp/"+ self.new_img_name new_img.save(aux) self.image = PhotoImage(file=aux) self.setImage() def click(self, event): if not self.ponto1: self.canvas.create_oval(event.x, event.y, event.x+5, event.y+5, fill="red") self.ponto1 = (event.x,event.y) else: if not self.ponto2: self.canvas.create_oval(event.x, self.ponto1[1], event.x+5, self.ponto1[1]+5, fill="red") self.ponto2 = (event.x,self.ponto1[1]) pontos = [self.ponto1[0]+1,self.ponto1[1]+2, self.ponto2[0]+1,self.ponto2[1]+2] self.canvas.create_line(pontos, tags="theline", fill='red') x = (self.ponto2[0] + self.ponto1[0]) / 2 self.canvas.create_text(x, self.ponto1[1]+8, text="1 umm") def remove_highlight(self,event): self.canvas.delete("highlight") def highlight(self, item): bbox = self.canvas.bbox(item) self.canvas.delete("highlight") if bbox: i = self.canvas.create_rectangle( bbox, fill="white", tag="highlight" ) self.canvas.lower(i, item) def has_focus(self): return self.canvas.focus() def has_selection(self): return self.canvas.tk.call(self.canvas._w, 'select', 'item') def set_focus(self, event): if self.canvas.type(CURRENT) != "text": return self.highlight(CURRENT) self.canvas.focus_set() self.canvas.focus(CURRENT) self.canvas.select_from(CURRENT, 0) self.canvas.select_to(CURRENT, END) def set_cursor(self, event): item = self.has_focus() if not item: return x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) self.canvas.icursor(item, "@%d,%d" % (x, y)) self.canvas.select_clear() def handle_key(self, event): item = self.has_focus() if not item: return insert = self.canvas.index(item, INSERT) if event.char >= " ": if self.has_selection(): self.canvas.dchars(item, SEL_FIRST, SEL_LAST) self.canvas.select_clear() self.canvas.insert(item, "insert", event.char) self.highlight(item) elif event.keysym == "BackSpace": if self.has_selection(): self.canvas.dchars(item, SEL_FIRST, SEL_LAST) self.canvas.select_clear() else: if insert > 0: self.canvas.dchars(item, insert-1, insert) self.highlight(item) elif event.keysym == "Home": self.canvas.icursor(item, 0) self.canvas.select_clear() elif event.keysym == "End": self.canvas.icursor(item, END) self.canvas.select_clear() elif event.keysym == "Right": self.canvas.icursor(item, insert+1) self.canvas.select_clear() elif event.keysym == "Left": self.canvas.icursor(item, insert-1) self.canvas.select_clear() else: pass
class MazePlannerCanvas(Frame): """ MazePlannerCanvas contains the main frontend workhorse functionality of the entire application. it allows the user to graphically place nodes and define the edges between them """ def __init__(self, parent, status=None, manager=DataStore()): """ Construct an instance of the MazePlannerCanvas :param parent: The parent widget that the mazePlannerCanvas will sit in :param status: The statusbar that will receive mouse updates :type manager: DataStore :return: """ Frame.__init__(self, parent) self._manager = manager self._canvas = Canvas(self, bg="grey", cursor="tcross") self._canvas.pack(fill=BOTH, expand=1) self._commands = { (ControlSpecifier.DRAG_NODE, ExecutionStage.START) : self._begin_node_drag, (ControlSpecifier.CREATE_EDGE, ExecutionStage.START) : self._begin_edge, (ControlSpecifier.DRAG_NODE, ExecutionStage.END) : self._end_node_drag, (ControlSpecifier.CREATE_EDGE, ExecutionStage.END) : self._end_edge, (ControlSpecifier.DRAG_NODE, ExecutionStage.EXECUTE) : self._execute_drag, (ControlSpecifier.CREATE_EDGE, ExecutionStage.EXECUTE) : self._execute_edge, (ControlSpecifier.MENU, ExecutionStage.EXECUTE) : self._launch_menu, (ControlSpecifier.CREATE_NODE, ExecutionStage.EXECUTE) : self.create_new_node, } self._commands = load_controls(self._commands) self._edge_cache = \ { "x_start" : None, "y_start" : None, "x_end" : None, "y_end" : None, "item_start" : None, "item_end" : None, "edge" : None } self._command_cache = None self._cache = \ { "item" : None, "x" : 0, "y" : 0, "event" : None } self._status = status self._edge_bindings = {} self._node_listing = {} self._object_listing = {} self._curr_start = None self._construct(parent) def _construct(self, parent): """ Construct all of the event bindings and callbacks for mouse events """ self._canvas.focus_set() self._canvas.bind("<B1-Motion>", lambda event, m_event=Input_Event.DRAG_M1: self._handle_mouse_events(m_event, event)) self._canvas.bind("<B2-Motion>", lambda event, m_event=Input_Event.DRAG_M2: self._handle_mouse_events(m_event, event)) self._canvas.bind("<B3-Motion>", lambda event, m_event=Input_Event.DRAG_M3: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonPress-2>", lambda event, m_event=Input_Event.CLICK_M2: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonRelease-2>", lambda event, m_event=Input_Event.RELEASE_M2: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonPress-1>", lambda event, m_event=Input_Event.CLICK_M1: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonPress-3>", lambda event, m_event=Input_Event.CLICK_M3: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonRelease-1>", lambda event, m_event=Input_Event.RELEASE_M1: self._handle_mouse_events(m_event, event)) self._canvas.bind("<ButtonRelease-3>", lambda event, m_event=Input_Event.RELEASE_M3: self._handle_mouse_events(m_event, event)) self._canvas.bind("<Return>", lambda event, m_event=Input_Event.RETURN: self._handle_mouse_events(m_event, event)) self._canvas.bind("<Double-Button-1>", lambda event, m_event=Input_Event.D_CLICK_M1: self._handle_mouse_events(m_event, event)) self._canvas.bind("<Double-Button-2>", lambda event, m_event=Input_Event.D_CLICK_M2: self._handle_mouse_events(m_event, event)) self._canvas.bind("<Double-Button-3>", lambda event, m_event=Input_Event.D_CLICK_M3: self._handle_mouse_events(m_event, event)) self._canvas.bind("<Motion>", lambda event, m_event=None : self._handle_mot(m_event, event)) self._canvas.bind("<Enter>", lambda event: self._canvas.focus_set()) self._canvas.bind("<space>", lambda event, m_event=Input_Event.SPACE: self._handle_mouse_events(m_event, event)) def _handle_mot(self, m_event, event): """ Callback function to handle movement of the mouse Function updates the mouse location status bar as well as setting cache values to the current location of the mouse :m_event: The specifier for the type of event that has been generated :event: The tk provided event object """ event.x = int(self._canvas.canvasx(event.x)) event.y = int(self._canvas.canvasy(event.y)) self._status.set_text("Mouse X:" + str(event.x) + "\tMouse Y:" + str(event.y)) item = self._get_current_item((event.x, event.y)) if self._is_node(item): Debug.printi("Node: " + str(item), Debug.Level.INFO) if self._is_edge(item): d_x = self._edge_bindings[item].x_start - self._edge_bindings[item].x_end d_y = self._edge_bindings[item].y_start - self._edge_bindings[item].y_end square = (d_x * d_x) + (d_y * d_y) distance = int(math.sqrt(square)) Debug.printi("Edge: " + str(item) + " | Source: " + str(self._edge_bindings[item].item_start) + " | Target: " + str(self._edge_bindings[item].item_end) + " | Length: " + str(distance)) self._cache["x"] = event.x self._cache["y"] = event.y def _handle_mouse_events(self, m_event, event): """ Function that routes mouse events to the appropriate handlers Prints logging and UI information about the state of the mouse and then routes the mouse event to the appropriate handler :m_event: The specifier for the tupe of event that has been generated :event: The tk provided event object """ event.x = int(self._canvas.canvasx(event.x)) event.y = int(self._canvas.canvasy(event.y)) self._status.set_text("Mouse X:" + str(self._cache["x"]) + "\tMouse Y:" + str(self._cache["y"])) Debug.printet(event, m_event, Debug.Level.INFO) self._cache["event"] = event try: self._commands[m_event]((event.x, event.y)) except KeyError: Debug.printi("Warning, no control mapped to " + m_event, Debug.Level.ERROR) self._command_cache = m_event def _begin_node_drag(self, coords): """ Handles starting operations for dragging a node Updates the cache information regarding a node drag event, we will used this cache value as the handle on which node to update the information for :coords: The mouse coordinates associated with this event """ # Determine which node has been selected, cache this information item = self._get_current_item(coords) if item in self._node_listing: self._update_cache(item, coords) def _end_node_drag(self, coords): """ Performs actions to complete a node drag operation Validates node location, and other associated object information and updates the cache when a node drag is completed :coords: The coordinates associated with this event """ if self._cache["item"] is None: return # Obtain the final points x = coords[0] y = coords[1] item = self._cache["item"] self._validate_node_position(coords) container = self._manager.request(DataStore.DATATYPE.NODE, item) container.x_coordinate = x container.y_coordinate = y self._manager.inform(DataStore.EVENT.NODE_EDIT, container.empty_container(), self._cache["item"]) Debug.printi("Node " + str(self._cache["item"]) + " has been moved", Debug.Level.INFO) # Clean the cache self._clear_cache(coords) def _validate_node_position(self, coords): """ if x < 0: x = 0 if y < 0: y = 0 if x > self._canvas.winfo_width(): x = self._canvas.winfo_width()-25 if y > self._canvas.winfo_height(): y = self._canvas.winfo_height()-25 self._canvas.move(item, x, y) """ pass def _execute_drag(self, coords): """ Updates object position on canvas when user is dragging a node :param coords: The coordinates associated with this event """ # Abort drag if the item is not a node if self._cache["item"] not in self._node_listing: return # Update the drawing information delta_x = coords[0] - self._cache["x"] delta_y = coords[1] - self._cache["y"] # move the object the appropriate amount as long as the drag event has not been done on the empty canvas if not self._cache["item"] is None: self._canvas.move(self._cache["item"], delta_x, delta_y) # record the new position self._cache["x"] = coords[0] self._cache["y"] = coords[1] self._update_attached_edges(self._cache["item"], coords) self._update_attached_objects(self._cache["item"], coords) def _update_attached_objects(self, item, coords): if item not in self._object_listing: return container = self._manager.request(DataStore.DATATYPE.OBJECT, item) container.x_coordinate = coords[0] container.y_coordinate = coords[1] self._manager.inform(DataStore.EVENT.OBJECT_EDIT, container.empty_container(), item) def _update_attached_edges(self, node, coords): """ Updates all associated edges related to a node drag event :param node: The node that has been dragged :param coords: The mouse coordinates which are the new coordinates of the node """ # Go through dictionary and gather list of all attached edge bindings for a node start_bindings = [] for key, binding in self._edge_bindings.iteritems(): if binding.item_start == node: start_bindings.append(binding) end_bindings = [] for key, binding in self._edge_bindings.iteritems(): if binding.item_end == node: end_bindings.append(binding) # Adjust the bindings with this node as the starting edge for binding in start_bindings: self._canvas.delete(binding.edge) del self._edge_bindings[binding.edge] old_edge = binding.edge binding.edge = self._canvas.create_line(coords[0], coords[1], binding.x_end, binding.y_end, tags="edge", activefill="RoyalBlue1", tag="edge") self._edge_bindings[binding.edge] = binding self._manager.update_key(DataStore.EVENT.EDGE_EDIT, binding.edge, old_edge) binding.x_start = coords[0] binding.y_start = coords[1] # Adjust the bindings with this node as the ending edge for binding in end_bindings: self._canvas.delete(binding.edge) del self._edge_bindings[binding.edge] old_edge = binding.edge binding.edge = self._canvas.create_line(binding.x_start, binding.y_start, coords[0], coords[1], tags="edge", activefill="RoyalBlue1", tag="edge") self._edge_bindings[binding.edge] = binding self._manager.update_key(DataStore.EVENT.EDGE_EDIT, binding.edge, old_edge) binding.x_end = coords[0] binding.y_end = coords[1] # Remember to adjust all of the edges so that they sit under the node images self._canvas.tag_lower("edge") def _launch_menu(self, coords): """ Callback function in response to the pressing of the Return key Launches a context menu based on the location of the mouse :param coords: :return: """ # Configure the "static" menu entries -- they can't be static without seriously destroying readability # due to the Python version that is being used -.- so now it has to be not optimal until I find a better # solution p_menu = Menu(self._canvas) item = self._get_current_item((self._cache["x"], self._cache["y"])) updated_coords = self._canvas_to_screen((self._cache["x"], self._cache["y"])) if item is None: # No node is currently selected, create the general menu p_menu.add_command(label="Place Room", command=lambda: self.create_new_node((self._cache["x"], self._cache["y"]))) p_menu.add_command(label="Delete All", command=lambda: self.delete_all()) p_menu.tk_popup(updated_coords[0], updated_coords[1]) return if self._is_node(item): # Create the node specific menu p_menu.add_command(label="Place Object", command=lambda: self._mark_object((self._cache["x"], self._cache["y"]))) p_menu.add_command(label="Edit Room", command=lambda: self._selection_operation((self._cache["x"], self._cache["y"]))) p_menu.add_command(label="Delete Room", command=lambda: self.delete_node(self._get_current_item((self._cache["x"], self._cache["y"])))) p_menu.add_command(label="Mark as start", command=lambda: self._mark_start_node(self._get_current_item((self._cache["x"], self._cache["y"])))) if self._is_object(item): # Launch the node menu as well as an an added option for selecting stuff to edit an object p_menu.add_command(label="Edit Object", command=lambda: self._edit_object(coords)) p_menu.add_command(label="Delete Object", command=lambda: self._delete_object(self._get_current_item((self._cache["x"], self._cache["y"])))) p_menu.delete(0) p_menu.tk_popup(updated_coords[0], updated_coords[1]) return if self._is_edge(item): p_menu.add_command(label="Edit Corridor", command=lambda: self._selection_operation((self._cache["x"], self._cache["y"]))) p_menu.add_command(label="Delete Corridor", command=lambda: self.delete_edge(self._get_current_item((self._cache["x"], self._cache["y"])))) p_menu.tk_popup(updated_coords[0], updated_coords[1]) return self._clear_cache(coords) def _edit_object(self, coords): """ Awkward moment when you find a threading related bug in the Tkinter library, caused by some Tcl issue or something like that. The below line must be left commented out otherwise the window_wait call in the dialog will crash out with a Tcl ponter based issue :/ item = self._get_current_item((self._cache["x"], self._cache["y"])) This means that we can only use the mouse to edit objects """ item = self._get_current_item(coords) if item not in self._object_listing: Debug.printi("Not a valid object to edit", Debug.Level.ERROR) return obj = ObjectDialog(self, coords[0] + 10, coords[1] + 10, populator=self._manager.request(DataStore.DATATYPE.OBJECT, item)) Debug.printi("Editing object " + str(item), Debug.Level.INFO) self._manager.inform(DataStore.EVENT.OBJECT_EDIT, obj._entries, item) Debug.printi("Editing object " + str(item), Debug.Level.INFO) def _delete_object(self, item): if item not in self._object_listing: Debug.printi("Object does not exist to delete", Debug.Level.ERROR) return del self._object_listing[item] self._manager.inform(DataStore.EVENT.OBJECT_DELETE, data_id=item) self._canvas.itemconfig(item, outline="red", fill="black", activeoutline="black", activefill="red") def _mark_object(self, coords, prog=False, data=None): """ Mark a node as containing an object :param coords: :return: """ # Retrieve the item item = self._get_current_item(coords) if not prog: if item not in self._node_listing: Debug.printi("Invalid object placement selection", Debug.Level.ERROR) return if item in self._object_listing: Debug.printi("This room already has an object in it", Debug.Level.ERROR) return # Retrieve its coordinates # Launch the object maker dialog obj = ObjectDialog(self, coords[0] + 10, coords[1] + 10, populator=Containers.ObjectContainer(key_val={ "x_coordinate" : coords[0], "y_coordinate" : coords[1], "name" : "Object_"+str(item), "mesh" : None, "scale" : None })) entries = obj._entries else: entries = { "x_coordinate": coords[0], "y_coordinate": coords[1], "name": data["name"], "mesh": data["mesh"], "scale": data["scale"] } # Save informatoin to the manager self._manager.inform(DataStore.EVENT.OBJECT_CREATE, entries, item) self._object_listing[item] = item self._canvas.itemconfig(item, fill="blue") Debug.printi("Object created in room " + str(item), Debug.Level.INFO) def _valid_edge_cache(self): """ Return true if the edge cache contains a valid edge descriptor A valid edge descriptor is when the edge has a valid starting node, if the edge does not contain a valid starting node, this means that the edge was not created in the proper manner and should thus be ignored by any edge operations """ valid = not self._edge_cache["item_start"] == (None,) return valid def _canvas_to_screen(self, coords): """ Convert canvas coordinates into screen coordinates :param coords: The current canvas coordinates :return: """ """ # upper left corner of the visible region x0 = self._canvas.winfo_rootx() y0 = self._canvas.winfo_rooty() # given a canvas coordinate cx/cy, convert it to window coordinates: wx0 = x0 + coords[0] wy0 = y0 + coords[1] # upper left corner of the visible region x0 = self._canvas.canvasx(0) y0 = self._canvas.canvasy(0) # given a canvas coordinate cx/cy, convert it to window coordinates: wx0 = coords[0] - x0 wy0 = coords[1] - y0 #""" return (self._cache["event"].x_root, self._cache["event"].y_root) def _begin_edge(self, coords): """ Begin recording information regarding the placement of an edge :param coords: The coordinates associated with this event """ # Record the starting node self._edge_cache["item_start"] = self._get_current_item((self._cache["x"], self._cache["y"])) # Abort the operation if the item was not a valid node to be selecting if self._edge_cache["item_start"] is None or self._edge_cache["item_start"] not in self._node_listing: self._clear_edge_cache() return self._edge_cache["x_start"] = self._cache["x"] self._edge_cache["y_start"] = self._cache["y"] def _end_edge(self, coords, prog=False, data=None): """ Perform the operations required to complete an edge creation operation :param coords: :return: """ # Check if the cursor is over a node, if so continue, else abort curr = self._get_current_item((coords[0], coords[1])) if not prog: if curr is None or not self._valid_edge_cache() or curr not in self._node_listing: # Abort the edge creation process self._canvas.delete(self._edge_cache["edge"]) self._clear_edge_cache() return # Check if this edge already exists in the program if self._check_duplicate_edges(self._edge_cache["item_start"], curr): self.delete_edge(self._edge_cache["edge"]) Debug.printi("Multiple edges between rooms not permitted", Debug.Level.ERROR) return #Ensure that edges arent made between the same room if curr == self._edge_cache["item_start"]: Debug.printi("Cannot allow paths starting and ending in the same room", Debug.Level.ERROR) return self._canvas.tag_lower("edge") self._edge_cache["item_end"] = curr # Note that we use the edge ID as the key self._edge_bindings[self._edge_cache["edge"]] = EdgeBind(self._edge_cache) self._edge_bindings[self._edge_cache["edge"]].x_end = coords[0] self._edge_bindings[self._edge_cache["edge"]].y_end = coords[1] # Inform the manager if not prog: self._manager.inform( DataStore.EVENT.EDGE_CREATE, { "source" : self._edge_cache["item_start"], "target" : self._edge_cache["item_end"], "height" : None, "wall1" : { "height":Defaults.Edge.WALL_HEIGHT, "textures":{ Defaults.Wall.PATH: { "path":Defaults.Wall.PATH, "tile_x":Defaults.Wall.TILE_X, "tile_y":Defaults.Wall.TILE_Y, "height":None } } } if Defaults.Config.EASY_MAZE else None, "wall2" : { "height": Defaults.Edge.WALL_HEIGHT, "textures": { Defaults.Wall.PATH: { "path": Defaults.Wall.PATH, "tile_x": Defaults.Wall.TILE_X, "tile_y": Defaults.Wall.TILE_Y, "height":None } } } } if Defaults.Config.EASY_MAZE else None, self._edge_cache["edge"]) else: # We are programmatically adding the edges in self._manager.inform( DataStore.EVENT.EDGE_CREATE, { "source": self._edge_cache["item_start"], "target": self._edge_cache["item_end"], "height": None, "wall1": data["wall1"], "wall2": data["wall2"] }, self._edge_cache["edge"]) Debug.printi("Edge created between rooms " + str(self._edge_cache["item_start"]) + " and " + str(self._edge_cache["item_end"]) , Debug.Level.INFO) self._clear_edge_cache() self._clear_cache(coords) def _check_duplicate_edges(self, start_node, end_node): for binding in self._edge_bindings.itervalues(): if ( start_node == binding.item_start and end_node == binding.item_end )\ or ( start_node == binding.item_end and end_node == binding.item_start): return True return False def _execute_edge(self, coords): """ Perform the operations that occur during the motion of an edge drag :param coords: :return: """ # Update the line position # We will update the line position by deleting and redrawing if not self._valid_edge_cache(): return self._canvas.delete(self._edge_cache["edge"]) self._edge_cache["edge"] = self._canvas.create_line( \ self._edge_cache["x_start"], self._edge_cache["y_start"], coords[0]-1, coords[1]-1, tags="edge", activefill="RoyalBlue1", tag="edge") d_x = self._edge_cache["x_start"] - coords[0] d_y = self._edge_cache["y_start"] - coords[1] square = (d_x * d_x) + (d_y * d_y) distance = math.sqrt(square) Debug.printi("Current corridor distance: " + str(int(distance))) def _update_cache(self, item, coords): """ Update the local cache with the item id and coordinates of the mouse :param item: The item with which to update the cache :param coords: The current event coordinates """ self._cache["item"] = item self._cache["x"] = coords[0] self._cache["y"] = coords[1] def _clear_cache(self, coords): """ Clear the cache Set the cache values to the current mouse position and None the item :param coords: The coordinates of the mouse at that event time """ self._cache["item"] = None self._cache["x"] = coords[0] self._cache["y"] = coords[1] def _clear_edge_cache(self): """ Clear the edge cache to None for all values :return: """ self._edge_cache["x_start"] = None, self._edge_cache["y_start"] = None, self._edge_cache["x_end"] = None, self._edge_cache["y_end"] = None, self._edge_cache["item_start"] = None, self._edge_cache["item_end"] = None, self._edge_cache["edge"] = None def _get_current_item(self, coords): """ Return the item(if any) that the mouse is currently over :param coords: The current coordinates of the mouse :return: """ item = self._canvas.find_overlapping(coords[0]-1, coords[1]-1, coords[0]+1, coords[1]+1) if item is (): return None # Hacky solution # Return the first node that we come across, since they seem to be returned by tkinter # in reverse order to their visual positioning, we'll go through the list backwards for val in item[::-1]: if val in self._node_listing: return val # Else, just return the first item and be done with it return item[0] def _is_node(self, obj): """ Returns true if the supplied object is a node :param obj: The object id to id :return: """ return obj in self._node_listing def _is_edge(self, obj): """ Returns true if the supplied object is an edge :param obj: The object id to id :return: """ return obj in self._edge_bindings def _is_object(self, obj): """ Returns true if the supplied object is an object :param obj: The object id to id :return: """ return obj in self._object_listing def _get_obj_type(self, obj): """ Returns the Object type of the supplied object :param obj: The object to identify :return: """ if self._is_node(obj): return EditableObject.NODE if self._is_edge(obj): return EditableObject.EDGE if self._is_object(obj): return EditableObject.OBJECT return None def _selection_operation(self, coords): """ Contextually create or edit a node :param coords: :return: """ # Determine the item ID item = self._get_current_item(coords) self._cache["item"] = item true_coords = self._canvas_to_screen((self._cache["x"], self._cache["y"])) if self._is_node(item): Debug.printi("Node Selected : " + str(item) + " | Launching Editor", Debug.Level.INFO) # Make request from object manager using the tag assigned populator = self._manager.request(DataStore.DATATYPE.NODE, item) updated_node = NodeDialog(self, true_coords[0] + 10, true_coords[1] + 10, populator=populator) # post information to object manager, or let the dialog handle it, or whatever self._manager.inform(DataStore.EVENT.NODE_EDIT, updated_node._entries, item) return if self._is_edge(item): Debug.printi("Edge Selected : " + str(item) + " | Launching Editor", Debug.Level.INFO) # Make a request from the object manager to populate the dialog populator = self._manager.request(DataStore.DATATYPE.EDGE, item) updated_edge = EdgeDialog(self, true_coords[0] + 10, true_coords[1] + 10, populator=populator) # Make sure that information is posted to the object manager self._manager.inform(DataStore.EVENT.EDGE_EDIT, updated_edge._entries, item) return if self._is_object(item): self._edit_object(coords) return def create_new_node(self, coords, prog = False, data=None): """ Creates a new node on the Canvas and adds it to the datastore :param coords: :return: """ # Create the node on Canvas self._cache["item"] = self._canvas.create_rectangle(coords[0], coords[1], coords[0]+25, coords[1]+25, outline="red", fill="black", activeoutline="black", activefill="red", tag="node") self._node_listing[self._cache["item"]] = self._cache["item"] if not prog: if not Defaults.Config.EASY_MAZE: true_coords = self._canvas_to_screen((self._cache["x"], self._cache["y"])) new_node = NodeDialog(self, true_coords[0] + 25, true_coords[1] + 25, populator=Containers.NodeContainer( { "node_id": self._cache["item"], "x_coordinate": self._cache["x"], "y_coordinate": self._cache["y"], "room_texture": None, "wall_pictures": None })) entries = new_node._entries else: entries = { "node_id": self._cache["item"], "x_coordinate": self._cache["x"], "y_coordinate": self._cache["y"], "room_texture": Defaults.Node.ROOM_TEXTURE, "wall_pictures": None } else: pics = data[1] data = data[0] entries = { "node_id": data["id"], "x_coordinate": data["x"], "y_coordinate": data["y"], "room_texture": data["texture"], "wall_pictures": pics } # Inform the datastore self._manager.inform(DataStore.EVENT.NODE_CREATE, entries, self._cache["item"]) self._clear_cache(coords) def delete_all(self): """ Delete all nodes and associated edges and objects from the canvas """ # Iterate over each node in the node listing and delete it using delete node for key in self._node_listing.keys(): self.delete_node(key) # Delete any rouge edge bindings that may exist for binding in self._edge_bindings: self.delete_edge(binding) self._object_listing.clear() # Delete any naughty objects that are left self._canvas.delete("all") self._manager.inform(DataStore.EVENT.DELETE_ALL) def delete_node(self, node_id): """ Delete a node and all its associated edges and object from the canvas :param node_id: The tkinter id of the node to be deleted """ # Delete from our internal representations if node_id not in self._node_listing: return del self._node_listing[node_id] # Delete from the canvas self._canvas.delete(node_id) # Iterate through the edge bindings and delete all of those for key in self._edge_bindings.keys(): if self._edge_bindings[key].item_start == node_id or self._edge_bindings[key].item_end == node_id: self.delete_edge(key) # Inform the object manager that a node as been deleted if node_id in self._object_listing: self._delete_object(node_id) self._manager.inform(DataStore.EVENT.NODE_DELETE, data_id=node_id) def delete_edge(self, edge_id): """ Delete the specified edge from the MazeCanvas :param edge_id: The edge to be deleted :return: """ # Go through the edge bindings and delete the appropriate edge try: # try to delete the edge binding if it exists del self._edge_bindings[edge_id] except KeyError: # Terrible I know, but I dont have the time to find the root cause pass # Delete the edge from the canvas self._canvas.delete(edge_id) # Inform the object manager that an edge has been deleted self._manager.inform(DataStore.EVENT.EDGE_DELETE, data_id=edge_id) def _mark_start_node(self, node_id): """ Mark the passed in node as the starting node :param node_id: :return: """ # Print the debug information # Mark as the new starting node on the canvas, first check that it is a node if node_id in self._node_listing: Debug.printi("Node:" + str(node_id) + " has been marked as the new starting node", Debug.Level.INFO) if self._curr_start is not None: # Return the old starting node to its normal colour self._canvas.itemconfig(self._curr_start, outline="red", fill="black", activeoutline="black", activefill="red") self._curr_start = node_id self._canvas.itemconfig(node_id, outline="black", fill="green", activeoutline="green", activefill="black") # Inform the object manager that there is a new starting node environment_container = self._manager.request(DataStore.DATATYPE.ENVIRONMENT) environment_container.start_node = node_id self._manager.inform(DataStore.EVENT.ENVIRONMENT_EDIT, environment_container)
class Application(Frame, object): """The application main class.""" WIDTH, HEIGHT = 1280, 720 BG = 'white' FONT = 'Verdana' FILE_OPEN_OPTIONS = { 'mode': 'rb', 'title': 'Choose *.json file', 'defaultextension': '.json', 'filetypes': [('JSON file', '*.json')] } DEFAULTS = 'default_settings.yaml' def __init__(self, master=None): """Creates application main window with sizes self.WIDTH and self.HEIGHT. :param master: instance - Tkinter.Tk instance """ super(Application, self).__init__(master) self.master.title('Engine Game') self.master.geometry('{}x{}'.format(self.WIDTH, self.HEIGHT)) self.master.protocol('WM_DELETE_WINDOW', self.exit) self.source = None self._map = None self.points = None self.lines = None self.captured_point = None self.x0 = None self.y0 = None self.scale_x = None self.scale_y = None self.font_size = None self.coordinates = {} self.captured_lines = {} self.canvas_obj = AttrDict() self.icons = { 0: PhotoImage(file=join('icons', 'player_city.png')), 1: PhotoImage(file=join('icons', 'city.png')), 2: PhotoImage(file=join('icons', 'market.png')), 3: PhotoImage(file=join('icons', 'store.png')), 4: PhotoImage(file=join('icons', 'point.png')), 5: PhotoImage(file=join('icons', 'player_train.png')), 6: PhotoImage(file=join('icons', 'train.png')), 7: PhotoImage(file=join('icons', 'crashed_train.png')), 8: PhotoImage(file=join('icons', 'collision.png')), 9: PhotoImage(file=join('icons', 'play.png')), 10: PhotoImage(file=join('icons', 'play_pressed.png')), 11: PhotoImage(file=join('icons', 'stop.png')), 12: PhotoImage(file=join('icons', 'stop_pressed.png')) } self.queue_requests = { 0: self.set_status_bar, 1: self.set_player_idx, 2: self.build_map, 3: self.refresh_map, 4: self.set_available_games, 99: self.bot_control } self.settings_window = None if exists(expanduser(self.DEFAULTS)): with open(expanduser(self.DEFAULTS), 'r') as cfg: defaults = DefaultsDict.from_yaml(cfg) self.host = None if not defaults.host else str(defaults.host) self.port = None if not defaults.port else int(defaults.port) self.timeout = None if not defaults.timeout else int(defaults.timeout) self.username = None if not defaults.username else str(defaults.username) self.password = None if not defaults.password else str(defaults.password) else: self.host, self.port, self.timeout, self.username, self.password = None, None, None, None, None self.player_idx = None self.posts = {} self.trains = {} self.select_game_window = False self.available_games = None self.game = None self.num_players = None self.num_turns = None self.bot = Bot() self.bot_thread = None self.menu = Menu(self) filemenu = Menu(self.menu) filemenu.add_command(label='Open file', command=self.file_open) filemenu.add_command(label='Server settings', command=self.open_server_settings) filemenu.add_command(label='Select game', command=self.select_game) filemenu.add_command(label='Exit', command=self.exit) self.menu.add_cascade(label='Menu', menu=filemenu) master.config(menu=self.menu) self._status_bar = StringVar() self.label = Label(master, textvariable=self._status_bar) self.label.pack() self.frame = Frame(self) self.frame.bind('<Configure>', self._resize_frame) self.canvas = Canvas(self.frame, bg=self.BG, scrollregion=(0, 0, self.winfo_width(), self.winfo_height())) self.canvas.bind('<Button-1>', self._capture_point) self.canvas.bind('<Motion>', self._move_point) self.canvas.bind('<B1-ButtonRelease>', self._release_point) self.canvas.bind('<Configure>', self._resize_canvas) hbar = Scrollbar(self.frame, orient=HORIZONTAL) hbar.pack(side=BOTTOM, fill=X) hbar.config(command=self.canvas.xview) vbar = Scrollbar(self.frame, orient=VERTICAL) vbar.pack(side=RIGHT, fill=Y) vbar.config(command=self.canvas.yview) self.canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set) self.canvas.pack(fill=BOTH, expand=True) self.play = Label(self.canvas, bg='white') self.play.configure(image=self.icons[9]) self.play.bind('<Button-1>', self._play_press) self.play.bind('<B1-ButtonRelease>', self._play_release) self.stop = Label(self.canvas, bg='white') self.stop.configure(image=self.icons[11]) self.stop.bind('<Button-1>', self._stop_press) self.stop.bind('<B1-ButtonRelease>', self._stop_release) self.frame.pack(fill=BOTH, expand=True) self.weighted = IntVar(value=1) self.weighted_check = Checkbutton(self, text='Proportionally to length', variable=self.weighted, command=self._proportionally) self.weighted_check.pack(side=LEFT) self.show_weight = IntVar() self.show_weight_check = Checkbutton(self, text='Show length', variable=self.show_weight, command=self.show_weights) self.show_weight_check.pack(side=LEFT) self.pack(fill=BOTH, expand=True) self.requests_executor() self.get_available_games() self.set_status_bar('Click Play to start the game') self.play.place(rely=0.5, relx=0.5, anchor=CENTER) @property def map(self): """Returns the actual map.""" return self._map @map.setter def map(self, value): """Clears previously drawn map and assigns a new map to self._map.""" self.clear_map() self.canvas.configure(scrollregion=(0, 0, self.canvas.winfo_width(), self.canvas.winfo_height())) self.x0, self.y0 = self.canvas.winfo_width() / 2, self.canvas.winfo_height() / 2 self._map = value @staticmethod def midpoint(x_start, y_start, x_end, y_end): """Calculates a midpoint coordinates between two points. :param x_start: int - x coordinate of the start point :param y_start: int - y coordinate of the start point :param x_end: int - x coordinate of the end point :param y_end: int - y coordinate of the end point :return: 2-tuple of a midpoint coordinates """ return (x_start + x_end) / 2, (y_start + y_end) / 2 def _resize_frame(self, event): """Calculates new font size each time frame size changes. :param event: Tkinter.Event - Tkinter.Event instance for Configure event :return: None """ self.font_size = int(0.0125 * min(event.width, event.height)) def _resize_canvas(self, event): """Redraws map each time Canvas size changes. Scales map each time visible part of Canvas is enlarged. :param event: Tkinter.Event - Tkinter.Event instance for Configure event :return: None """ if self.map: k = min(float(event.width) / float(self.x0 * 2), float(event.height) / float(self.y0 * 2)) self.scale_x, self.scale_y = self.scale_x * k, self.scale_y * k self.x0, self.y0 = self.x0 * k, self.y0 * k self.redraw_map() self.redraw_trains() x_start, y_start, x_end, y_end = self.canvas.bbox('all') x_start = 0 if x_start > 0 else x_start y_start = 0 if y_start > 0 else y_start self.canvas.configure(scrollregion=(x_start, y_start, x_end, y_end)) def _proportionally(self): """Rebuilds map and redraws trains.""" self.build_map() self.redraw_trains() def _capture_point(self, event): """Stores captured point and it's lines. :param event: Tkinter.Event - Tkinter.Event instance for ButtonPress event :return: None """ x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) obj_ids = self.canvas.find_overlapping(x - 5, y - 5, x + 5, y + 5) if not obj_ids: return for obj_id in obj_ids: if obj_id in self.canvas_obj.point.keys(): self.captured_point = obj_id point_idx = self.canvas_obj.point[obj_id]['idx'] self.captured_lines = {} for line_id, attr in self.canvas_obj.line.items(): if attr['start_point'] == point_idx: self.captured_lines[line_id] = 'start_point' if attr['end_point'] == point_idx: self.captured_lines[line_id] = 'end_point' if self.weighted.get(): self.weighted.set(0) def _release_point(self, event): """Writes new coordinates for a moved point and resets self.captured_point and self.captured_lines. :param event: Tkinter.Event - Tkinter.Event instance for ButtonRelease event :return: None """ if self.captured_point: idx = self.canvas_obj.point[self.captured_point]['idx'] x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) self.coordinates[idx] = (x, y) self.points[idx]['x'], self.points[idx]['y'] = (x - self.x0) / self.scale_x, (y - self.y0) / self.scale_y self.captured_point = None self.captured_lines = {} def _move_point(self, event): """Moves point and its lines. Moves weights if self.show_weight is set to 1. In case some point is moved beyond Canvas border Canvas scrollregion is resized correspondingly. :param event: Tkinter.Event - Tkinter.Event instance for Motion event :return: None """ if self.captured_point: new_x, new_y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) self.canvas.coords(self.captured_point, new_x, new_y) indent_y = self.icons[self.canvas_obj.point[self.captured_point]['icon']].height() / 2 + self.font_size if self.canvas_obj.point[self.captured_point]['text_obj']: self.canvas.coords(self.canvas_obj.point[self.captured_point]['text_obj'], new_x, new_y - indent_y) self.coordinates[self.canvas_obj.point[self.captured_point]['idx']] = (new_x, new_y) self.canvas.configure(scrollregion=self.canvas.bbox('all')) for line_id, attr in self.captured_lines.items(): line_attrs = self.canvas_obj.line[line_id] if attr == 'start_point': x, y = self.coordinates[line_attrs['end_point']] self.canvas.coords(line_id, new_x, new_y, x, y) else: x, y = self.coordinates[line_attrs['start_point']] self.canvas.coords(line_id, x, y, new_x, new_y) if self.show_weight.get(): mid_x, mid_y = self.midpoint(new_x, new_y, x, y) self.canvas.coords(line_attrs['weight_obj'][1], mid_x, mid_y) r = self.font_size * len(str(line_attrs['weight'])) self.canvas.coords(line_attrs['weight_obj'][0], mid_x - r, mid_y - r, mid_x + r, mid_y + r) self.redraw_trains() def _play_press(self, _): """Draws play button pressed icon.""" self.play.configure(image=self.icons[10]) def _play_release(self, _): """Draws play button icon and calls bot_control method.""" self.play.configure(image=self.icons[9]) self.bot_control() def _stop_press(self, _): """Draws stop button pressed icon.""" self.stop.configure(image=self.icons[12]) def _stop_release(self, _): """Draws stop buton icon and calls bot_control method.""" self.stop.configure(image=self.icons[11]) self.bot_control() def set_player_idx(self, value): """Sets a player idx value.""" self.player_idx = value def file_open(self): """Opens file dialog and builds and draws a map once a file is chosen. Stops bot if its started.""" path = tkFileDialog.askopenfile(parent=self.master, **self.FILE_OPEN_OPTIONS) if path: if self.bot_thread: self.bot_control() self.posts, self.trains = {}, {} self.source = path.name self.weighted_check.configure(state=NORMAL) self.build_map() def open_server_settings(self): """Opens server settings window.""" ServerSettings(self, title='Server settings') def select_game(self): """Opens select game window.""" self.select_game_window = True SelectGame(self, title='Select game') self.select_game_window = False self.set_status_bar('Click Play to start the game') def exit(self): """Closes application and stops bot if its started.""" if self.bot_thread: self.bot_control() self.master.destroy() def bot_control(self): """Starts bot for playing the game or stops it if it is started.""" if not self.bot_thread: self.bot_thread = Thread(target=self.bot.start, kwargs={ 'host': self.host, 'port': self.port, 'time_out': self.timeout, 'username': self.username, 'password': self.password, 'game': self.game, 'num_players': self.num_players, 'num_turns': self.num_turns}) self.bot_thread.start() else: self.bot.stop() self.bot_thread.join() self.bot_thread = None def get_available_games(self): """Requests a list of available games.""" if self.select_game_window: self.bot.get_available_games(host=self.host, port=self.port, time_out=self.timeout) self.after(1000, self.get_available_games) def set_available_games(self, games): """Sets new value for available games list.""" self.available_games = games def set_status_bar(self, value): """Assigns new status bar value and updates it. :param value: string - status bar string value :return: None """ self._status_bar.set(value) self.label.update() def build_map(self, source=None): """Builds and draws new map. :param source: string - source string; could be JSON string or path to *.json file. :return: None """ if source: self.source = source if self.source: self.map = Graph(self.source, weighted=self.weighted.get()) self.set_status_bar('Map title: {}'.format(self.map.name)) self.points, self.lines = self.map.get_coordinates() self.draw_map() def draw_map(self): """Draws map by prepared coordinates.""" self.draw_lines() self.draw_points() def clear_map(self): """Clears previously drawn map and resets coordinates and scales.""" self.canvas.delete('all') self.scale_x, self.scale_y = None, None self.coordinates = {} def redraw_map(self): """Redraws existing map by existing coordinates.""" if self.map: self.coordinates = {} for obj_id in self.canvas_obj.line: self.canvas.delete(obj_id) self.draw_lines() self.redraw_points() def redraw_points(self): """Redraws map points by existing coordinates.""" if self.map: for obj_id, attrs in self.canvas_obj.point.items(): if attrs['text_obj']: self.canvas.delete(attrs['text_obj']) self.canvas.delete(obj_id) self.draw_points() def redraw_trains(self): """Redraws existing trains.""" if self.trains and hasattr(self.canvas_obj, 'train'): for obj_id, attrs in self.canvas_obj.train.items(): self.canvas.delete(attrs['text_obj']) self.canvas.delete(obj_id) self.draw_trains() @prepare_coordinates def draw_points(self): """Draws map points by prepared coordinates.""" point_objs = {} captured_point_idx = self.canvas_obj.point[self.captured_point]['idx'] if self.captured_point else None for idx in self.points.keys(): x, y = self.coordinates[idx] if self.posts and idx in self.posts.keys(): post_type = self.posts[idx]['type'] if post_type == 1: status = '{}/{} {}/{} {}/{}'.format(self.posts[idx]['population'], self.posts[idx]['population_capacity'], self.posts[idx]['product'], self.posts[idx]['product_capacity'], self.posts[idx]['armor'], self.posts[idx]['armor_capacity']) elif post_type == 2: status = '{}/{}'.format(self.posts[idx]['product'], self.posts[idx]['product_capacity']) else: status = '{}/{}'.format(self.posts[idx]['armor'], self.posts[idx]['armor_capacity']) image_id = 0 if post_type == 1 and self.posts[idx]['player_idx'] == self.player_idx else post_type point_id = self.canvas.create_image(x, y, image=self.icons[image_id]) y -= (self.icons[post_type].height() / 2) + self.font_size text_id = self.canvas.create_text(x, y, text=status, font="{} {}".format(self.FONT, self.font_size)) else: post_type = 4 point_id = self.canvas.create_image(x, y, image=self.icons[post_type]) text_id = None point_objs[point_id] = {'idx': idx, 'text_obj': text_id, 'icon': post_type} self.captured_point = point_id if idx == captured_point_idx else self.captured_point self.canvas_obj['point'] = point_objs @prepare_coordinates def draw_lines(self): """Draws map lines by prepared coordinates and shows their weights if self.show_weight is set to 1.""" line_objs, captured_lines_idx = {}, {} if self.captured_lines: for line_id in self.captured_lines.keys(): captured_lines_idx[self.canvas_obj.line[line_id]['idx']] = line_id for idx, attrs in self.lines.items(): x_start, y_start = self.coordinates[attrs['start_point']] x_stop, y_stop = self.coordinates[attrs['end_point']] line_id = self.canvas.create_line(x_start, y_start, x_stop, y_stop) line_objs[line_id] = {'idx': idx, 'weight': attrs['weight'], 'start_point': attrs['start_point'], 'end_point': attrs['end_point'], 'weight_obj': ()} if idx in captured_lines_idx.keys(): self.captured_lines[line_id] = self.captured_lines.pop(captured_lines_idx[idx]) self.canvas_obj['line'] = line_objs self.show_weights() @prepare_coordinates def draw_trains(self): """Draws trains by prepared coordinates.""" trains = {} for train in self.trains.values(): start_point = self.lines[train['line_idx']]['start_point'] end_point = self.lines[train['line_idx']]['end_point'] weight = self.lines[train['line_idx']]['weight'] position = train['position'] x_start, y_start = self.coordinates[start_point] x_end, y_end = self.coordinates[end_point] delta_x, delta_y = int((x_start - x_end) / weight) * position, int((y_start - y_end) / weight) * position x, y = x_start - delta_x, y_start - delta_y if train['cooldown'] > 0: icon = 7 status = None else: icon = 5 if train['player_idx'] == self.player_idx else 6 status = '{}/{}'.format(train['goods'], train['goods_capacity']) indent_y = self.icons[icon].height() / 2 train_id = self.canvas.create_image(x, y - indent_y, image=self.icons[icon]) text_id = self.canvas.create_text(x, y - (2 * indent_y + self.font_size), text=status, font="{} {}".format(self.FONT, self.font_size)) if status else None trains[train_id] = {'icon': icon, 'text_obj': text_id} self.canvas_obj['train'] = trains def show_weights(self): """Shows line weights when self.show_weight is set to 1 and hides them when it is set to 0.""" if not self.canvas_obj: return if self.show_weight.get(): for line in self.canvas_obj.line.values(): if line['weight_obj']: for obj in line['weight_obj']: self.canvas.itemconfigure(obj, state='normal') else: x_start, y_start = self.coordinates[line['start_point']] x_end, y_end = self.coordinates[line['end_point']] x, y = self.midpoint(x_start, y_start, x_end, y_end) value = line['weight'] size = self.font_size r = int(size) * len(str(value)) oval_id = self.canvas.create_oval(x - r, y - r, x + r, y + r, fill=self.BG, width=0) text_id = self.canvas.create_text(x, y, text=value, font="{} {}".format(self.FONT, str(size))) line['weight_obj'] = (oval_id, text_id) else: for line in self.canvas_obj.line.values(): if line['weight_obj']: for obj in line['weight_obj']: self.canvas.itemconfigure(obj, state='hidden') def requests_executor(self): """Dequeues and executes requests. Assigns corresponding label to bot control button.""" if not self.bot.queue.empty(): request_type, request_body = self.bot.queue.get_nowait() if request_type == 99 and request_body: self.open_server_settings() request_body = None if request_body is not None: self.queue_requests[request_type](request_body) else: self.queue_requests[request_type]() if self.bot_thread and self.bot_thread.is_alive(): if self.play.place_info(): self.play.place_forget() self.stop.place(rely=0.99, relx=0.995, anchor=SE) else: if self.stop.place_info(): self.stop.place_forget() self.play.place(rely=0.5, relx=0.5, anchor=CENTER) self.after(50, self.requests_executor) def refresh_map(self, dynamic_objects): """Refreshes map with passed dynamic objects. :param dynamic_objects: dict - dict of dynamic objects :return: None """ for post in dynamic_objects['posts']: self.posts[post['point_idx']] = post for train in dynamic_objects['trains']: self.trains[train['idx']] = train self.redraw_points() self.redraw_trains()
class Viewer(Frame): def __init__(self, master,x=600,y=200, onLeft=None, onRight=None, **kwargs): self.root=master self.xsize=x self.ysize=y self.onLeft=onLeft self.onRight=onRight self.ratio=100. Frame.__init__(self, master,width=x,height=y, **kwargs) self.canvas = Canvas(self, width=x, height=y, background="white") self.xsb = Scrollbar(self, orient="horizontal", command=self.canvas.xview) self.ysb = Scrollbar(self, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set) self.canvas.configure(scrollregion=(0,0,x,y)) self.xsb.grid(row=1, column=0, sticky="ew") self.ysb.grid(row=0, column=1, sticky="ns") self.canvas.grid(row=0, column=0, sticky="nsew") self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) self.canvas.bind("<Button-1>", self.clickL) self.canvas.bind("<Button-3>", self.clickR) # This is what enables using the mouse: self.canvas.bind("<ButtonPress-1>", self.move_start) self.canvas.bind("<B1-Motion>", self.move_move) #linux scroll self.canvas.bind("<Button-4>", self.zoomerP) self.canvas.bind("<Button-5>", self.zoomerM) self.canvas.bind_all("<Prior>", self.zoomerP) self.canvas.bind_all("<Next>", self.zoomerM) self.canvas.bind_all("E", self.zoomExtens) #windows scroll self.canvas.bind_all("<MouseWheel>",self.zoomer) def reset(self): pass def set_title(self, title): self.root.title( title) #move def move_start(self, event): self.canvas.scan_mark(event.x, event.y) return True def move_move(self, event): self.canvas.scan_dragto(event.x, event.y, gain=1) return True #windows zoom def zoomer(self,event): if (event.delta > 0): self.ratio *= 1.1 elif (event.delta < 0): self.ratio *= 0.9 self.redraw() def redraw(self): self.canvas.delete("all") self.canvas.configure(scrollregion = self.canvas.bbox("all")) def zoomExtens(self, *args): x0,y0,x1,y1 = self.canvas.bbox("all") xlen=x1-x0 ylen=y1-y0 height=self.canvas.winfo_height() width=self.canvas.winfo_width() unfit=min(width/float(xlen), height/float(ylen)) self.ratio*=unfit self.redraw() # """" if xlen and ylen: self ratio*= self.canvas.scale("all", xlen/2., ylen/2., float(self.xsize)/xlen, float(self.ysize)/ylen) self.canvas.configure(scrollregion = self.canvas.bbox("all")) """ #linux zoom def zoomerP(self,event): self.canvas.scale("all", event.x, event.y, 1.1, 1.1) self.canvas.configure(scrollregion = self.canvas.bbox("all")) def zoomerM(self,event): self.canvas.scale("all", event.x, event.y, 0.9, 0.9) self.canvas.configure(scrollregion = self.canvas.bbox("all")) def pose2p(self, pose): x,y=pose[:2] return int(x*self.ratio), -int(y*self.ratio) def line2p(self, line): if type(line[0]) in (float, int): line=zip(line[::2],line[1::2]) return map(self.pose2p, line) def p2m(self, x, y): return x/self.ratio, -y/self.ratio def create_line(self, coords, **kwargs): return self.canvas.create_line(self.line2p(coords), **kwargs) def create_polygon(self, coords, **kwargs): return self.canvas.create_polygon(self.line2p(coords), **kwargs) def delete(self, object): self.canvas.delete(object) def clickL(self, event): if self.onLeft: x,y=self.p2m(int(self.canvas.canvasx(event.x)), int(self.canvas.canvasy(event.y))) self.onLeft(x,y) return True def clickR(self, event): if self.onRight: x,y=self.p2m(int(self.canvas.canvasx(event.x)), int(self.canvas.canvasy(event.y))) self.onRight(x,y) return True