def __init__(self, builder, changed_method): gtk.ScrolledWindow.__init__(self) self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.builder = builder self.event_box = EventBox() self.event_box.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.event_box.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.event_box.add_events(gtk.gdk.MOTION_NOTIFY) self.event_box.add_events(gtk.gdk.BUTTON1_MOTION_MASK) self.event_box.add_events(gtk.gdk.KEY_PRESS_MASK) self.event_box.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) self.event_box.connect('motion-notify-event', self.mouse_motion) self.event_box.connect('button-press-event', self.mouse_press) self.event_box.connect('button-release-event', self.mouse_release) self.event_box.connect('scroll-event', self.mouse_scroll) self.action = None self.menu = None self.box = None self.last_vertex_clicked = None self.last_position_clicked = None self.box_selecting = None self.changed = False self.changed_method = changed_method self.graph = GraphController() self.area = GraphArea(self.graph) self.event_box.add(self.area) self.add_with_viewport(self.event_box) self.area.show() self.event_box.show() self.show() # To UNDO and REDO actions self.states = [] self.state_index = None self.add_state() # Algorithm stuff self.algorithm_runner = None self.algorithm_playing = None self.algorithm_paused = None self.algorithm_states = []
class Graph(gtk.ScrolledWindow): def __init__(self, builder, changed_method): gtk.ScrolledWindow.__init__(self) self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.builder = builder self.event_box = EventBox() self.event_box.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.event_box.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.event_box.add_events(gtk.gdk.MOTION_NOTIFY) self.event_box.add_events(gtk.gdk.BUTTON1_MOTION_MASK) self.event_box.add_events(gtk.gdk.KEY_PRESS_MASK) self.event_box.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.POINTER_MOTION_HINT_MASK) self.event_box.connect('motion-notify-event', self.mouse_motion) self.event_box.connect('button-press-event', self.mouse_press) self.event_box.connect('button-release-event', self.mouse_release) self.event_box.connect('scroll-event', self.mouse_scroll) self.action = None self.menu = None self.box = None self.last_vertex_clicked = None self.last_position_clicked = None self.box_selecting = None self.changed = False self.changed_method = changed_method self.graph = GraphController() self.area = GraphArea(self.graph) self.event_box.add(self.area) self.add_with_viewport(self.event_box) self.area.show() self.event_box.show() self.show() # To UNDO and REDO actions self.states = [] self.state_index = None self.add_state() # Algorithm stuff self.algorithm_runner = None self.algorithm_playing = None self.algorithm_paused = None self.algorithm_states = [] def centralize_scroll(self, position=None): """Put both scrolls in center""" vadj = self.get_vadjustment() hadj = self.get_hadjustment() if not position: position = [(vadj.upper / 2), (hadj.upper / 2)] print([int(x) for x in position]) hadj.set_value(position[0] - (hadj.page_size / 2)) vadj.set_value(position[1] - (vadj.page_size / 2)) def zoom_in(self, center=None): if self.area.zoom < 1.7: self.area.zoom *= 1.1 self.do_zoom(lambda v: v * 1.1, center) def zoom_out(self, center=None): if self.area.zoom > 0.2: self.area.zoom /= 1.1 self.do_zoom(lambda v: v / 1.1, center) def zoom_default(self): old = self.area.zoom self.area.zoom = 1 self.do_zoom(lambda v: v / old) def do_zoom(self, op, center=None): width, height = self.area.get_size_request() vadj = self.get_vadjustment() hadj = self.get_hadjustment() width *= self.area.zoom height *= self.area.zoom if not center: offsetx, offsety = self.event_box.get_size_request() x = (hadj.get_value() + offsetx / 2) * self.area.zoom y = (vadj.get_value() + offsety / 2) * self.area.zoom center = [x, y] hadj.set_upper(width) vadj.set_upper(height) # self.centralize_scroll(center) hadj.set_value(op(hadj.get_value())) vadj.set_value(op(vadj.get_value())) self.area.queue_draw() def mouse_scroll(self, widget, event): width, height = self.area.get_size_request() vadj = self.get_vadjustment() hadj = self.get_hadjustment() if not (event.state & gtk.gdk.CONTROL_MASK): return center = [v / self.area.zoom for v in event.get_coords()] if event.direction == gtk.gdk.SCROLL_UP: self.zoom_in(center) elif event.direction == gtk.gdk.SCROLL_DOWN: self.zoom_out(center) width, height = self.area.get_size_request() vadj = self.get_vadjustment() hadj = self.get_hadjustment() def set_changed(self, value): if self.changed != value: self.changed = value self.changed_method(self) def add_vertex(self): self.graph.add_vertex(self.last_position_clicked) self.add_state() self.action = None self.area.queue_draw() def remove_vertex(self): to_be_removed = list(self.graph.selected_vertices()) list(map(lambda vertex: self.graph.remove_vertex(vertex), to_be_removed)) self.add_state() self.action = None self.area.queue_draw() def edit_vertex(self): if len(self.graph.selected_vertices()) == 1: vertex = self.graph.selected_vertices()[0] self.graph.deselect_vertex(vertex) vertex_edit = Vertex(self, vertex) def add_edge(self): if len(self.graph.selected_vertices()) == 1: vertex = self.graph.find_by_position(self.last_position_clicked) if vertex != None and vertex != self.graph.selected_vertices()[0]: self.graph.add_edge(self.graph.selected_vertices()[0], vertex) self.graph.deselect_vertex(self.graph.selected_vertices()[0]) self.add_state() self.action = None self.area.queue_draw() return True if len(self.graph.selected_vertices()) > 1: for i in range(len(self.graph.selected_vertices())): for j in range(i, len(self.graph.selected_vertices())): vertex1 = self.graph.selected_vertices()[i] vertex2 = self.graph.selected_vertices()[j] if vertex1 != vertex2: self.graph.add_edge(vertex1, vertex2) if self.graph.directed: self.graph.add_edge(vertex2, vertex1) selected_vertices = list(self.graph.selected_vertices()) if len(selected_vertices): self.graph.deselect_all() self.add_state() for vertex in selected_vertices: self.graph.select_vertex(vertex) self.action = None self.area.queue_draw() return True self.action = "add_edge" return False def remove_edge(self): # TODO - Handle multiple edges if len(self.graph.selected_vertices()) == 1: vertex = self.graph.find_by_position(self.last_position_clicked) if vertex != None: edge = self.graph.find_edge(self.graph.selected_vertices()[0], vertex) if len(edge) > 0: self.graph.remove_edge(edge[0]) self.graph.deselect_vertex(self.graph.selected_vertices()[0]) self.add_state() self.action = None self.area.queue_draw() return True elif len(self.graph.selected_vertices()) > 1: for vertex1 in self.graph.selected_vertices(): for vertex2 in self.graph.selected_vertices(): if vertex1 != vertex2: edge = self.graph.find_edge(vertex1, vertex2) if len(edge) > 0: self.graph.remove_edge(edge[0]) self.graph.deselect_all() self.add_state() self.action = None self.area.queue_draw() return True return False def edit_edge(self): # TODO - Handle multiple edges if len(self.graph.selected_vertices()) == 2: v1 = self.graph.selected_vertices()[0] v2 = self.graph.selected_vertices()[1] edge = self.graph.find_edge(v1, v2) self.graph.deselect_vertex(v1) self.graph.deselect_vertex(v2) # TODO: This only works for 1 edge. 0 or more than 1 edges: fail. edge_edit = Edge(self, edge[0]) def select_area(self, event, area): if not area: return x, y, w, h = area vertices = self.graph.find_in_area(x, y, w, h) from gtk.gdk import CONTROL_MASK, SHIFT_MASK if not (event.state & CONTROL_MASK or event.state & SHIFT_MASK): self.graph.deselect_all() method = self.graph.select_vertex if (event.state & CONTROL_MASK): method = self.graph.toggle_vertex_selection list(map(lambda vertex: method(vertex), vertices)) def select_vertex(self, event): vertex = self.graph.find_by_position(self.last_position_clicked) from gtk.gdk import CONTROL_MASK, SHIFT_MASK if not (event.state & CONTROL_MASK or event.state & SHIFT_MASK) and (len(self.graph.selected_vertices()) > 0): if not vertex or not vertex.selected: self.graph.deselect_all() if vertex: if vertex.selected and (event.state & CONTROL_MASK or event.state & SHIFT_MASK): self.graph.deselect_vertex(vertex) else: self.graph.select_vertex(vertex) self.last_vertex_clicked = vertex else: self.box_selecting = self.last_position_clicked self.action = None def __block_event_box(self): if self.algorithm_runner: self.event_box.handler_block_by_func(self.mouse_motion) self.event_box.handler_block_by_func(self.mouse_press) self.event_box.handler_block_by_func(self.mouse_release) self.event_box.handler_block_by_func(self.mouse_scroll) else: self.event_box.handler_unblock_by_func(self.mouse_motion) self.event_box.handler_unblock_by_func(self.mouse_press) self.event_box.handler_unblock_by_func(self.mouse_release) self.event_box.handler_unblock_by_func(self.mouse_scroll) def algorithm_play(self): if self.algorithm_runner: self.graph.deselect_all() self.algorithm_playing = True if self.algorithm_paused: self.algorithm_paused = False else: self.algorithm_runner.play() self.queue_draw() gobject.timeout_add(500, self.algorithm_next, True) def algorithm_pause(self): if self.algorithm_runner: if self.algorithm_playing: self.algorithm_runner.pause() self.algorithm_playing = False self.algorithm_paused = True def algorithm_load(self, Algorithm): if self.algorithm_runner: self.algorithm_runner.stop() self.algorithm_runner = Algorithm(self) self.__block_event_box() def algorithm_next(self, auto=False): if self.algorithm_runner: if auto and not self.algorithm_paused: next(self.algorithm_runner) self.queue_draw() if self.algorithm_playing and self.algorithm_runner.is_alive(): return True else: if not self.algorithm_playing or not self.algorithm_runner.is_alive(): next(self.algorithm_runner) self.queue_draw() self.algorithm_playing = False return False def algorithm_prev(self): if self.algorithm_runner: self.algorithm_runner.prev() self.queue_draw() if self.algorithm_playing: self.algorithm_playing = False self.algorithm_prev() def algorithm_stop(self): if self.algorithm_runner: self.algorithm_runner.stop() self.algorithm_runner = None self.queue_draw() self.__block_event_box() def algorithm_layout(self): self.graph.layout_graph(50) self.queue_draw() def add_state(self): state = pickle.dumps(self.graph) if not self.state_index: self.state_index = 0 for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "undo").set_sensitive(True) if (self.state_index < len(self.states) - 1): self.states = self.states[:self.state_index + 1] for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "redo").set_sensitive(False) self.states.append(state) if len(self.states) == 1: self.state_index = 0 else: self.state_index += 1 def prev_state(self): if (self.state_index > 0 and len(self.states) > 0): self.state_index -= 1 graph = self.states[self.state_index] state = pickle.loads(graph) if self.state_index == 0: for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "undo").set_sensitive(False) if len(self.states) > 1: for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "redo").set_sensitive(True) return state return None def next_state(self): if (self.state_index < len(self.states) - 1): self.state_index += 1 graph = self.states[self.state_index] state = pickle.loads(graph) if self.state_index == len(self.states) - 1: for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "redo").set_sensitive(False) if len(self.states) > 1: for place in ["menu_edit_", "toolbutton_"]: self.builder.get_object(place + "undo").set_sensitive(True) return state return None def undo(self): graph = self.prev_state() if graph: graph.path = self.graph.path graph.title = self.graph.title self.area.graph = graph self.graph = graph self.set_changed(True) self.queue_draw() def redo(self): graph = self.next_state() if graph: self.area.graph = graph self.graph = graph self.set_changed(True) self.queue_draw() def mouse_press(self, widget, event): self.last_position_clicked = [v / self.area.zoom for v in event.get_coords()] if event.button == 1: if self.action != None: self.set_changed(True) if self.action == None: self.select_vertex(event) if event.type == gtk.gdk._2BUTTON_PRESS: self.edit_vertex() elif self.action == "add_vertex": self.add_vertex() elif self.action == "remove_vertex": self.remove_vertex() elif self.action == "add_edge": if not self.add_edge(): self.select_vertex(event) elif self.action == "remove_edge": if not self.remove_edge(): self.select_vertex(event) elif event.button == 2: if self.action != None: self.set_changed(True) if self.action == None: vertex = self.graph.find_by_position(self.last_position_clicked) if vertex: self.select_vertex(event) self.action = "add_edge" else: self.add_vertex() elif self.action == "add_edge": if not self.add_edge(): self.select_vertex(event) elif event.button == 3: self.right_click_menu(event) self.area.queue_draw() self.mouse_motion(widget, event) def right_click_menu(self, event): vertex = self.graph.find_by_position(self.last_position_clicked) if len(self.graph.selected_vertices()) == 0 and vertex: self.graph.select_vertex(vertex) def execute_action(event, action): action() if not self.menu: self.menu = gtk.Menu() self.menu_add_edge = gtk.MenuItem(_("_Add edge")) self.menu_remove_edge = gtk.MenuItem(_("_Remove edge")) self.menu_add_vertex = gtk.MenuItem(_("_Add vertex")) self.menu_remove_vertex = gtk.MenuItem(_("_Remove vertex")) self.menu_edit_vertex = gtk.MenuItem(_("_Edit vertex settings")) self.menu_edit_edge = gtk.MenuItem(_("_Edit edge settings")) self.menu_add_edge.connect("activate", execute_action, self.add_edge) self.menu_remove_edge.connect("activate", execute_action, self.remove_edge) self.menu_add_vertex.connect("activate", execute_action, self.add_vertex) self.menu_remove_vertex.connect("activate", execute_action, self.remove_vertex) self.menu_edit_vertex.connect("activate", execute_action, self.edit_vertex) self.menu_edit_edge.connect("activate", execute_action, self.edit_edge) self.menu.append(self.menu_add_vertex) self.menu.append(self.menu_remove_vertex) self.menu.append(gtk.SeparatorMenuItem()) self.menu.append(self.menu_add_edge) self.menu.append(self.menu_remove_edge) self.menu.append(gtk.SeparatorMenuItem()) self.menu.append(self.menu_edit_vertex) self.menu.append(self.menu_edit_edge) if vertex: self.menu_add_vertex.set_sensitive(False) self.menu_edit_vertex.set_sensitive(True) self.menu_remove_vertex.set_sensitive(True) self.menu_add_edge.set_sensitive(True) self.menu_remove_edge.set_sensitive(True) self.menu_edit_edge.set_sensitive(False) if len(self.graph.selected_vertices()) == 2: self.menu_edit_edge.set_sensitive(True) else: self.menu_add_vertex.set_sensitive(True) self.menu_edit_vertex.set_sensitive(False) self.menu_remove_vertex.set_sensitive(False) self.menu_add_edge.set_sensitive(len(self.graph.selected_vertices()) > 0) self.menu_remove_edge.set_sensitive(False) self.menu_edit_edge.set_sensitive(False) self.menu.show_all() self.menu.popup(None, None, None, event.button, event.time) def mouse_release(self, widget, event): selected_vertices = list(self.graph.selected_vertices()) if len(selected_vertices) > 0 and self.last_vertex_clicked: self.graph.deselect_all() self.add_state() for vertex in selected_vertices: self.graph.select_vertex(vertex) self.last_vertex_clicked = None self.box_selecting = None self.select_area(event, self.area.selected_area) self.area.selected_area = None self.area.queue_draw() def mouse_motion(self, widget, event): coords = [v / self.area.zoom for v in event.get_coords()] if self.box_selecting: x, y = self.box_selecting w, h = [e - s for e, s in zip(coords, self.box_selecting)] self.area.selected_area = (x, y, w, h) self.area.queue_draw() return selected_vertices = self.graph.selected_vertices() if self.action == "add_edge" and len(selected_vertices) == 1: start = selected_vertices[0].position end = coords self.area.adding_edge = (start, end) self.area.queue_draw() else: self.area.adding_edge = None if len(selected_vertices) > 0 and self.last_vertex_clicked: end_position = coords start_position = self.last_vertex_clicked.position delta_x = end_position[0] - start_position[0] delta_y = end_position[1] - start_position[1] self.set_changed(True) for vertex in selected_vertices: new_position = [vertex.position[0] + delta_x, vertex.position[1] + delta_y] vertex.position = new_position self.area.queue_draw()