Exemplo n.º 1
0
    def __init__(self, *args, **kw):
        super(GraphWidget, self).__init__(*args, **kw)
        #pygame.display.set_caption('Graphui | Enough')
        self.dot = Dot()
        self.init_control_map()
        
        self.dragging_enabled = False
        self.connecting = False
        self.disconnecting = False
        self.preserve_aspect_ratio = True
        self.bezier_points = 30
        self.popup_menu_widget = None
        
        self.dot_prog_num = 0
        
        self.status_font = pygame.font.SysFont('serif',min(self.size.final.y/30, 12))
        self.rendered_status_texts = []
        self.set_status_text(message,15)
        self.set_status_text('CTRL-H for help', 15)
        self.set_status_text('Right-Click for menu', 15)

        self.pan_start_pos = None
        self.reset_zoom_pan()

        # TODO get rid of shape in Widget (move it to some ShapeWidget or whatever)
        #self.shape = None
        #self.params.fore_color = None
        self.params.back_color = None
        self.params.focus_back_color = (20,20,50)
        self.params.hover_back_color = (10,10,25)
        self.params.focus_fore_color = (150,150,255)
        self.params.enabled = False 

        self._last_group = 0

        self.rect_image = load_image("images/square_purple_gray.png")
        self.focused_rect_image = load_image("images/square.png")
        self.hovered_rect_image = load_image("images/square_purple.png")
Exemplo n.º 2
0
class GraphWidget(Widget):
    def __init__(self, *args, **kw):
        super(GraphWidget, self).__init__(*args, **kw)
        #pygame.display.set_caption('Graphui | Enough')
        self.dot = Dot()
        self.init_control_map()
        
        self.dragging_enabled = False
        self.connecting = False
        self.disconnecting = False
        self.preserve_aspect_ratio = True
        self.bezier_points = 30
        self.popup_menu_widget = None
        
        self.dot_prog_num = 0
        
        self.status_font = pygame.font.SysFont('serif',min(self.size.final.y/30, 12))
        self.rendered_status_texts = []
        self.set_status_text(message,15)
        self.set_status_text('CTRL-H for help', 15)
        self.set_status_text('Right-Click for menu', 15)

        self.pan_start_pos = None
        self.reset_zoom_pan()

        # TODO get rid of shape in Widget (move it to some ShapeWidget or whatever)
        #self.shape = None
        #self.params.fore_color = None
        self.params.back_color = None
        self.params.focus_back_color = (20,20,50)
        self.params.hover_back_color = (10,10,25)
        self.params.focus_fore_color = (150,150,255)
        self.params.enabled = False 

        self._last_group = 0

        self.rect_image = load_image("images/square_purple_gray.png")
        self.focused_rect_image = load_image("images/square.png")
        self.hovered_rect_image = load_image("images/square_purple.png")
        
    def reset_zoom_pan(self):
        self.pan_offset = Point((0,0))
        self.pos_zoom = 1
        self.size_zoom = 0.8
        self.update_layout()
        
    def init_control_map(self):
        zoom_amount = 1.3
        self.control_map = {pygame.K_w: ("View","Zoom in", partial(self.zoom, zoom_amount)),
                            pygame.K_q: ("View","Zoom out", partial(self.zoom, 1.0/zoom_amount)),
                            pygame.K_z: ("Edit","Undo", self.undo),
                            pygame.K_y: ("Edit","Redo", self.redo),
                            pygame.K_p: ("Tools","Record (toggle)", self.toggle_record),
                            pygame.K_i: ("Tools","Save snapshot image", partial(self.save_snapshot_image, "graphui.snapshot.bmp")),
                            pygame.K_o: ("File","Output DOT", self.output_dot_description),
                            pygame.K_EQUALS: ("View","More curve points", partial(self.change_curve_resolution,
                                                                                 3)),
                            pygame.K_MINUS: ("View","Less curve points", partial(self.change_curve_resolution,
                                                                               -3)),
                            pygame.K_k: ("View","Switch layout engine", partial(self.toggle_layout_engine, 1)),
                            pygame.K_a: ("Edit","Create new node", self.create_new_node),
                            pygame.K_d: ("Edit","Delete selected", self.delete_focused),
                            
                            pygame.K_e: ("View", "Stretch (toggle)", self.toggle_aspect_ratio),
                            pygame.K_r: ("View", "Reset zoom & pan", self.reset_zoom_pan),
                            
                            pygame.K_s: ("File", "Save", self.save),
                            pygame.K_l: ("File", "Load", self.load),

                            pygame.K_g: ("Edit", "Group selected", self.assign_to_group),
                            pygame.K_f: ("Edit", "Ungroup selected", self.unassign_to_group),
                            pygame.K_b: ("View", "Toggle group names", self.toggle_show_group_names),

                            pygame.K_h: ("Help","Show help", self.show_help),
                            }

    def control_map_by_menu(self):
        menu_dict = {}
        for key, (menu, name, handler) in sorted(self.control_map.iteritems()):
            menu_dict.setdefault(menu, []).append((key, name,handler))
        return menu_dict
        
    @undoable_method
    def add_nodes(self, nodes):
        self.set_status_text("Add %d nodes" % (len(nodes),))
        for node in nodes:
            w = NodeWidget(start_pos=self.size.current*0.5)
            w.set_node(node)
            self.add_widget(w)
        self.update_layout()
        return partial(self.remove_nodes, nodes)
    @undoable_method
    def remove_nodes(self, nodes):
        self.set_status_text("Remove %d nodes" % (len(nodes),))
        for node in nodes:
            removed_edges = node.disconnect_all()
            for edge in removed_edges:
                # The edge itself has been removed from the graph
                # already, remove only the widget
                self._remove_edge_widget_of_edge(edge)
            self.remove_widget(node.value.widget)
        self.update_layout()
        return partial(self.add_nodes, nodes)

    @undoable_method
    def zoom(self, zoom):
        self.set_status_text("Zoom %d" % (zoom,))
        self.pos_zoom *= zoom
        self.size_zoom *= zoom
        self.update_layout()
        if zoom != 0:
            return partial(self.zoom, 1.0/zoom)

    def delete_focused(self):
        if self.focused_widgets is None:
            return
        nodes = []
        for w in self.focused_widgets:
            if isinstance(w, NodeWidget):
                nodes.append(w.node)
            elif isinstance(w, EdgeWidget):
                self._remove_edge(w.edge)
        if nodes:
            self.remove_nodes(nodes)
        self.unset_focus()
        self.update_layout()

    #@undoable_method it's not so useful to undo this...
    def toggle_layout_engine(self, dirc):
        self.dot_prog_num = (self.dot_prog_num + dirc) % len(self.dot.layout_programs)
        prog = self.dot.layout_programs[self.dot_prog_num]
        self.set_status_text("Layout engine: %r" % (prog,))
        self.dot.set_process(prog)
        self.update_layout()
        #return partial(self.toggle_layout_engine, -dirc)
        
    def paint_connector(self, parent_offset, surface, color, widgets):
        mpos = mouse_pos()
        for w in widgets:
            cpos = w.center_pos() + self.pos.current + parent_offset
            paint_arrowhead_by_direction(surface, color, cpos, mpos)
            pygame.draw.aalines(surface, color, False,
                                [tuple(cpos), tuple(mpos)], True)
        
    def paint_widgets(self, event):
        super(GraphWidget, self).paint_widgets(event)
        surface = event.surface
        if self.connecting:
            self.paint_connector(event.parent_offset, surface, (150,250,150), [n.value.widget for n in self.connecting_sources])
        if self.disconnecting:
            self.paint_connector(event.parent_offset, surface, (250,150,150), [n.value.widget for n in self.disconnecting_sources])
        self.paint_status_text(event.parent_offset, event.surface)
            
        
    def key_down(self, when, event):
        super(GraphWidget, self).key_down(when, event)
        if when == 'post':
            return False
        
        e = event.pygame_event
        if (e.mod & pygame.KMOD_CTRL):
            self.handle_control_key(e)
            return True
        else:
            return False

    def toggle_record(self):
        if not self.record:
            self.start_record()
        else:
            self.stop_record()

    def create_new_node(self):
        self.add_nodes([Graph.Node(GraphElementValue(''))])

    def output_dot_description(self):
        nodes, groups = self._get_nodes_and_groups()
        print '\n'
        print Graph.generate_dot(groups)
        print '\n'

    def change_curve_resolution(self, amount_to_add):
        self.bezier_points += amount_to_add
        if self.bezier_points < 4:
            self.bezier_points = 4
        self.update_layout()

    def handle_control_key(self, e):
        scancode = getattr(e, 'scancode', None)

        key = e.key
        if scancode is not None:
            if scancode not in scancode_map:
                print 'Unknown scancode', scancode, 'key=', e.key, 'unicode=', e.unicode
            key = scancode_map.get(scancode, e.key)
        if key in self.control_map:
            menu_name, name, handler = self.control_map[key]
            handler()

    def _connect_modifier_used(self, mods=None):
        return self._modkey_used(pygame.KMOD_SHIFT)
    def _pan_modifier_used(self, mods=None):
        #return self._modkey_used(pygame.KMOD_ALT)
        b1, b2, b3 = pygame.mouse.get_pressed()
        return b1 & b3
        
    def mouse_down(self, when, event):
        res = super(GraphWidget, self).mouse_down(when, event)
        if res == 'pre':
            return res
        
        e = event.pygame_event
        mods = pygame.key.get_mods()
        if self._pan_modifier_used(mods):
            self.pan_start_offset = self.pan_offset
            self.pan_start_pos = mouse_pos() 
            return True
            
        multiselect = self._multiselect_modifier_used(mods)
        if self.focused_widgets and self._connect_modifier_used(mods):
            if e.button == 1:
                self.connecting_sources = [w.node for w in self.focused_widgets if isinstance(w, NodeWidget)]
                if self.connecting_sources:
                    self.connecting = True
            elif e.button == 3:
                self.disconnecting_sources = [w.node for w in self.focused_widgets if isinstance(w, NodeWidget)]
                if self.disconnecting_sources:
                    self.disconnecting = True
            self.unset_focus()
        return True
            
    def mouse_motion(self, when, event):
        res = super(GraphWidget, self).mouse_motion(when, event)
        if when == 'post':
            return res

        e = event.pygame_event
        if self._pan_modifier_used():
            p = mouse_pos()
            self.pan_offset = p - self.pan_start_pos + self.pan_start_offset
            self.update_layout()
        return False
            
                
    def mouse_up(self, when, event):
        res = super(GraphWidget, self).mouse_up(when, event)
        if when == 'post':
            return res

        if self.popup_menu_widget not in self.focused_widgets and self.popup_menu_widget != self.hovered_widget:
            self.close_popup()
        
        e = event.pygame_event
        if self._pan_modifier_used():
            self.update_layout()
            return False
        
        if self.connecting:
            self.connecting = False
            target = self.hovered_widget
            if isinstance(target, NodeWidget):
                target = target.node
                self.connect_nodes(self.connecting_sources, target)
            self.connecting_source = None
        elif self.disconnecting:
            self.disconnecting = False
            target = self.hovered_widget
            if isinstance(target, NodeWidget):
                target = target.node
                self.disconnect_nodes(self.disconnecting_sources, target)
            self.disconnecting_source = None
        elif e.button == 3:
            # right click
            menu_seq = []
            menu_dict = self.control_map_by_menu()
            for menu in menu_dict:
                current_submenu = [(name,handler) for (key,name,handler) in menu_dict[menu]]
                menu_seq.append((menu, current_submenu))
            self.show_popup(menu_seq)
        else:
            return False

        return True

    def close_popup(self):
        if self.popup_menu_widget:
            self.remove_widget(self.popup_menu_widget)
            self.popup_menu_widget = None

    def show_popup(self, menu_list):
        self.close_popup()
        item_width=180
        item_height=30
        margin=2
        all_items = []
        
        menu = RowWidget()
        
        # menu_list Should be a sequence:
        # ('submenu title', (('label', handler), ('label2', handler2), etc...)], ), ('nextsubmenu', ....)
        for submenu_title, name_handlers in menu_list:
            step = min(3, len(name_handlers))+1

            submenu = RowWidget()
            submenu.transpose()
            submenu.margin = margin

            times = len(name_handlers)/step
            if len(name_handlers) % step:
                times += 1
            for i in xrange(times):
                current_values = tuple(name_handlers[i*step:i*step+step])
                row = make_row_label_menu(current_values, partial(self.popup_item_chosen, submenu), text_size=20)

                for (label,obj), widget in zip(current_values, row.widgets):
                    all_items.append(((label,obj),widget))
                    widget.shape=None

                row.margin=margin
                row.painting_z_order = NodeWidget.painting_z_order + 1
                row.transpose()
                row.shape = None
                submenu.add_widget_to_row(row)

            menu.add_widget_to_row(submenu)

        menu.params.in_drag_mode = True
        menu.pos.final = self.size.final / 2 - menu.size.final / 2
        menu.painting_z_order = NodeWidget.painting_z_order+1

        #menu.shape_image = self.rect_image
        #menu.hovered_shape_image = self.hovered_rect_image
        #menu.focused_shape_image = self.focused_rect_image

        self.add_widget(menu)
        self.popup_menu_widget = menu
        return all_items
        
    def popup_item_chosen(self, menu, sub_menu, (label,value), clicked_widget, event):
        if ('mouse' in event.type) and not clicked_widget.in_bounds(event.pos + clicked_widget.pos.current):
            return
        if value:
            self.close_popup()
            value()