Example #1
0
 def get_control_widgets( self ):
     self.t = GraphUpdater( self.cfg, self.updater, self.usercfg,
                            self.info_bar, self.xdot )
     self.t.start()
     return self.xdot.get()
Example #2
0
class ControlGraph(object):
    """
Dependency graph suite control interface.
    """
    def __init__(self, cfg, updater, usercfg, info_bar, get_right_click_menu,
                 log_colors, insert_task_popup ):
        # NOTE: this view has separate family Group and Ungroup buttons
        # instead of a single Group/Ungroup toggle button, unlike the
        # other views the graph view can display intermediate states
        # between fully grouped and fully ungrouped (well, so can the
        # tree view, but in that case the toggle button determines
        # whether it displays a flat list or a proper tree view).

        self.cfg = cfg
        self.updater = updater
        self.usercfg = usercfg
        self.info_bar = info_bar
        self.get_right_click_menu = get_right_click_menu
        self.log_colors = log_colors
        self.insert_task_popup = insert_task_popup

        self.gcapture_windows = []

        self.xdot = xdot_widgets()
        self.xdot.widget.connect( 'clicked', self.on_url_clicked )
        self.xdot.widget.connect_after( 'motion-notify-event', self.on_motion_notify )
        self.last_url = None

    def get_control_widgets( self ):
        self.t = GraphUpdater( self.cfg, self.updater, self.usercfg,
                               self.info_bar, self.xdot )
        self.t.start()
        return self.xdot.get()

    def toggle_graph_disconnect( self, w, update_button ):
        if w.get_active():
            self.t.graph_disconnect = True
            w.set_image( gtk.image_new_from_stock( gtk.STOCK_DISCONNECT,
                                                   gtk.ICON_SIZE_SMALL_TOOLBAR ) )
            self._set_tooltip( w, "Click to reconnect" )
            update_button.set_sensitive(True)
        else:
            self.t.graph_disconnect = False
            w.set_image( gtk.image_new_from_stock( gtk.STOCK_CONNECT,
                                                   gtk.ICON_SIZE_SMALL_TOOLBAR ) )
            self._set_tooltip( w, "Click to disconnect" )
            update_button.set_sensitive(False)
        return True

    def graph_update( self, w ):
        self.t.action_required = True
 
    def on_url_clicked( self, widget, url, event ):
        if event.button != 3:
            return False

        m = re.match( 'base:(.*)', url )
        if m:
            # base graph node
            task_id = m.groups()[0]
            self.right_click_menu( event, task_id, type='base graph task' )
            return

        self.right_click_menu( event, url, type='live task' )

    def on_motion_notify( self, widget, event ):
        """Add a new tooltip when the cursor moves in the graph."""
        url = self.xdot.widget.get_url( event.x, event.y )
        if url == self.last_url:
            return False
        self.last_url = url
        if not hasattr(self.xdot.widget, "set_tooltip_text"):
            # Unfortunately, the older gtk.Tooltips doesn't work well here.
            # gtk.Widget.set_tooltip_text was introduced at PyGTK 2.12
            return False
        if url is None:
            self.xdot.widget.set_tooltip_text(None)
            return False
        url = unicode(url.url)
        m = re.match( 'base:(.*)', url )
        if m:
            #print 'BASE GRAPH'
            task_id = m.groups()[0]
            #warning_dialog( 
            #        task_id + "\n"
            #        "This task is part of the base graph, taken from the\n"
            #        "suite config file (suite.rc) dependencies section, \n" 
            #        "but it does not currently exist in the running suite." ).warn()
            self.xdot.widget.set_tooltip_text(self.t.get_summary(task_id))
            return False

        # URL is task ID
        #print 'LIVE TASK'
        self.xdot.widget.set_tooltip_text(self.t.get_summary(url))
        return False

    def stop(self):
        self.t.quit = True

    def right_click_menu( self, event, task_id, type='live task' ):
        name, ctime = task_id.split(TaskID.DELIM)

        menu = gtk.Menu()
        menu_root = gtk.MenuItem( task_id )
        menu_root.set_submenu( menu )

        timezoom_item_direct = gtk.MenuItem( 'Focus on ' + ctime )
        timezoom_item_direct.connect( 'activate', self.focused_timezoom_direct, ctime )

        timezoom_item = gtk.MenuItem( 'Focus on Range' )
        timezoom_item.connect( 'activate', self.focused_timezoom_popup, task_id )

        timezoom_reset_item = gtk.MenuItem( 'Focus Reset' )
        timezoom_reset_item.connect( 'activate', self.focused_timezoom_direct, None )

        group_item = gtk.ImageMenuItem( 'Group' )
        img = gtk.image_new_from_stock( 'group', gtk.ICON_SIZE_MENU )
        group_item.set_image(img)
        group_item.set_sensitive( not self.t.have_leaves_and_feet or name not in self.t.feet )
        group_item.connect( 'activate', self.grouping, name, True )

        ungroup_item = gtk.ImageMenuItem( 'UnGroup' )
        img = gtk.image_new_from_stock( 'ungroup', gtk.ICON_SIZE_MENU )
        ungroup_item.set_image(img)
        ungroup_item.set_sensitive( not self.t.have_leaves_and_feet or name not in self.t.leaves )
        ungroup_item.connect( 'activate', self.grouping, name, False )

        ungroup_rec_item = gtk.ImageMenuItem( 'Recursive UnGroup' )
        img = gtk.image_new_from_stock( 'ungroup', gtk.ICON_SIZE_MENU )
        ungroup_rec_item.set_image(img)
        ungroup_rec_item.set_sensitive( not self.t.have_leaves_and_feet or name not in self.t.leaves )
        ungroup_rec_item.connect( 'activate', self.grouping, name, False, True )

        title_item = gtk.MenuItem( 'Task: ' + task_id.replace("_", "__") )
        title_item.set_sensitive(False)
        menu.append( title_item )

        menu.append( gtk.SeparatorMenuItem() )

        if type is not 'live task':
            insert_item = gtk.ImageMenuItem( 'Insert ...' )
            img = gtk.image_new_from_stock(  gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_MENU )
            insert_item.set_image(img)
            menu.append( insert_item )
            insert_item.connect( 'button-press-event',
                                lambda *a: self.insert_task_popup(
                                           is_fam=(name in self.t.descendants), 
                                           name=name, tag=ctime ))
            menu.append( gtk.SeparatorMenuItem() )

        menu.append( timezoom_item_direct )
        menu.append( timezoom_item )
        menu.append( timezoom_reset_item )

        menu.append( gtk.SeparatorMenuItem() )
        menu.append( group_item )
        menu.append( ungroup_item )
        menu.append( ungroup_rec_item )

        if type == 'live task':
            is_fam = (name in self.t.descendants)
            default_menu = self.get_right_click_menu( task_id, hide_task=True,
                                                      task_is_family=is_fam )
            for item in default_menu.get_children():
                default_menu.remove( item )
                menu.append( item )

        menu.show_all()
        menu.popup( None, None, None, event.button, event.time )

        # TODO - popup menus are not automatically destroyed and can be
        # reused if saved; however, we need to reconstruct or at least
        # alter ours dynamically => should destroy after each use to
        # prevent a memory leak? But I'm not sure how to do this as yet.)

        return True

    def grouping( self, w, name, group, rec=False ):
        self.t.ungroup_recursive = rec
        self.t.group_all = False
        self.t.ungroup_all = False
        if group:
            self.t.group = [name]
            self.t.ungroup = []
        else:
            self.t.ungroup = [name]
            self.t.group = []
        self.t.action_required = True
        self.t.best_fit = True

    def rearrange( self, col, n ):
        cols = self.ttreeview.get_columns()
        for i_n in range(len(cols)):
            if i_n == n: 
                cols[i_n].set_sort_indicator(True)
            else:
                cols[i_n].set_sort_indicator(False)
        # col is cols[n]
        if col.get_sort_order() == gtk.SORT_ASCENDING:
            col.set_sort_order(gtk.SORT_DESCENDING)
        else:
            col.set_sort_order(gtk.SORT_ASCENDING)
        self.ttreestore.set_sort_column_id(n, col.get_sort_order()) 

    def get_menuitems( self ):
        """Return the menu items specific to this view."""
        items = []
        graph_range_item = gtk.MenuItem( 'Time Range Focus ...' )
        items.append( graph_range_item )
        graph_range_item.connect( 'activate', self.graph_timezoom_popup )

        crop_item = gtk.CheckMenuItem( 'Toggle _Crop Base Graph' )
        items.append( crop_item )
        crop_item.set_active( self.t.crop )
        crop_item.connect( 'activate', self.toggle_crop )

        croprunahead_item = gtk.CheckMenuItem( 'Toggle Crop _Runahead Tasks' )
        items.append( croprunahead_item )
        croprunahead_item.set_active( self.t.croprunahead )
        croprunahead_item.connect( 'activate', self.toggle_croprunahead )

        menu_filter_item = gtk.ImageMenuItem( 'Task _Filtering ...' )
        img = gtk.image_new_from_stock(  gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU )
        menu_filter_item.set_image(img)
        items.append( menu_filter_item )
        menu_filter_item.connect( 'activate', self.filter_popup )

        self.menu_group_item = gtk.ImageMenuItem( '_Group All Families' )
        img = gtk.image_new_from_stock(  'group', gtk.ICON_SIZE_MENU )
        self.menu_group_item.set_image(img)
        items.append( self.menu_group_item )
        self.menu_group_item.connect( 'activate', self.group_all, True )

        self.menu_ungroup_item = gtk.ImageMenuItem( '_UnGroup All Families' )
        img = gtk.image_new_from_stock(  'ungroup', gtk.ICON_SIZE_MENU )
        self.menu_ungroup_item.set_image(img)
        items.append( self.menu_ungroup_item )
        self.menu_ungroup_item.connect( 'activate', self.group_all, False )

        menu_landscape_item = gtk.CheckMenuItem( '_Landscape Mode' )
        items.append( menu_landscape_item )
        menu_landscape_item.set_active( self.t.orientation == "LR" )
        menu_landscape_item.connect( 'activate', self.toggle_landscape_mode )

        igsui_item = gtk.CheckMenuItem( '_Ignore Suicide Triggers' )
        items.append( igsui_item )
        igsui_item.set_active( self.t.ignore_suicide )
        igsui_item.connect( 'activate', self.toggle_ignore_suicide_triggers )
 
        return items

    def _set_tooltip( self, widget, tip_text ):
        tip = gtk.Tooltips()
        tip.enable()
        tip.set_tip( widget, tip_text )

    def get_toolitems( self ):
        """Return the tool bar items specific to this view."""
        items = []
        for child in self.xdot.vbox.get_children():
            if isinstance(child, gtk.HButtonBox):
                self.xdot.vbox.remove(child)

        self.group_toolbutton = gtk.ToolButton()
        g_image = gtk.image_new_from_stock( 'group', gtk.ICON_SIZE_SMALL_TOOLBAR )
        self.group_toolbutton.set_icon_widget( g_image )
        self.group_toolbutton.connect( 'clicked', self.group_all, True )
        self._set_tooltip( self.group_toolbutton, "Graph View - Click to group all task families" )
        items.append( self.group_toolbutton )
 
        self.ungroup_toolbutton = gtk.ToolButton()
        g_image = gtk.image_new_from_stock( 'ungroup', gtk.ICON_SIZE_SMALL_TOOLBAR )
        self.ungroup_toolbutton.set_icon_widget( g_image )
        self.ungroup_toolbutton.connect( 'clicked', self.group_all, False )
        self._set_tooltip( self.ungroup_toolbutton, "Graph View - Click to ungroup all task families" )
        items.append( self.ungroup_toolbutton )
 
        zoomin_button = gtk.ToolButton( gtk.STOCK_ZOOM_IN )
        zoomin_button.connect( 'clicked', self.xdot.widget.on_zoom_in )
        zoomin_button.set_label( None )
        self._set_tooltip( zoomin_button, "Graph View - Zoom In" )
        items.append( zoomin_button )

        zoomout_button = gtk.ToolButton( gtk.STOCK_ZOOM_OUT )
        zoomout_button.connect( 'clicked', self.xdot.widget.on_zoom_out )
        zoomout_button.set_label( None )
        self._set_tooltip( zoomout_button, "Graph View - Zoom Out" )
        items.append( zoomout_button )
        
        zoomfit_button = gtk.ToolButton( gtk.STOCK_ZOOM_FIT )
        zoomfit_button.connect('clicked', self.xdot.widget.on_zoom_fit)
        zoomfit_button.set_label( None )
        self._set_tooltip( zoomfit_button, "Graph View - Best Fit" )
        items.append( zoomfit_button )

        zoom100_button = gtk.ToolButton( gtk.STOCK_ZOOM_100 )
        zoom100_button.connect('clicked', self.xdot.widget.on_zoom_100)
        zoom100_button.set_label( None )
        self._set_tooltip( zoom100_button, "Graph View - Normal Size" )
        items.append( zoom100_button )
       
        connect_button = gtk.ToggleButton()
        image = gtk.image_new_from_stock( gtk.STOCK_CONNECT, gtk.ICON_SIZE_SMALL_TOOLBAR )
        connect_button.set_image( image )
        connect_button.set_relief( gtk.RELIEF_NONE )
        self._set_tooltip( connect_button, "Graph View - Click to disconnect" )
        connect_item = gtk.ToolItem()
        connect_item.add( connect_button )
        items.append( connect_item )

        update_button = gtk.ToolButton( gtk.STOCK_REFRESH )
        update_button.connect( 'clicked', self.graph_update )
        update_button.set_label( None )
        update_button.set_sensitive( False )
        self._set_tooltip( update_button, "Graph View - Update graph" ) 
        items.append( update_button )
        
        connect_button.connect( 'clicked', self.toggle_graph_disconnect, update_button )

        return items
             
    def group_all( self, w, group ):
        if group:
            self.t.group_all = True
            self.t.ungroup_all = False
            if "graph" in self.cfg.ungrouped_views:
                self.cfg.ungrouped_views.remove("graph")
        else:
            self.t.ungroup_all = True
            self.t.group_all = False
            if "graph" not in self.cfg.ungrouped_views:
                self.cfg.ungrouped_views.append("graph")
        self.t.action_required = True
        self.t.best_fit = True

    def toggle_crop( self, w ):
        self.t.crop = not self.t.crop
        self.t.action_required = True

    def toggle_croprunahead( self, w ):
        self.t.croprunahead = not self.t.croprunahead
        self.t.action_required = True

    def toggle_landscape_mode( self, w ):
        """Change the orientation of the graph - 'portrait' or 'landscape'."""
        if self.t.orientation == "TB":  # Top -> bottom ordering
            self.t.orientation = "LR"  # Left -> right ordering
        elif self.t.orientation == "LR":
            self.t.orientation = "TB"
        self.t.action_required = True

    def toggle_ignore_suicide_triggers( self, w ):
        self.t.ignore_suicide = not self.t.ignore_suicide
        self.t.action_required = True

    def filter_popup( self, w ):
        window = gtk.Window()
        window.modify_bg( gtk.STATE_NORMAL, 
                gtk.gdk.color_parse( self.log_colors.get_color()))
        window.set_border_width(5)
        window.set_title( "Task Filtering")
        parent_window = self.xdot.widget.get_toplevel()
        if isinstance(parent_window, gtk.Window):
            window.set_transient_for( parent_window )
            window.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG )
        vbox = gtk.VBox()

        # TODO - error checking on date range given
        box = gtk.HBox()
        label = gtk.Label( 'Exclude (regex)' )
        box.pack_start( label, True )
        exclude_entry = gtk.Entry()
        box.pack_start (exclude_entry, True)
        vbox.pack_start( box )

        box = gtk.HBox()
        label = gtk.Label( 'Include (regex)' )
        box.pack_start( label, True )
        include_entry = gtk.Entry()
        box.pack_start (include_entry, True)
        vbox.pack_start( box )

        filterbox = gtk.HBox()

        # to initially filter out 'succeeded' and 'waiting' tasks
        #filter_states = [ 'waiting', 'succeeded' ]
        for st in task_state.legal:
            b = gtk.CheckButton( task_state.labels[st] )
            filterbox.pack_start(b)
            #if st in filter_states:
            #    b.set_active(False)
            #else:
            b.set_active(True)

        vbox.pack_start( filterbox )

        cancel_button = gtk.Button( "_Close" )
        cancel_button.connect("clicked", lambda x: window.destroy() )

        reset_button = gtk.Button( "_Reset (No Filtering)" )
        reset_button.connect("clicked", self.filter_reset )

        apply_button = gtk.Button( "_Apply" )
        apply_button.connect("clicked", self.filter,
                exclude_entry, include_entry, filterbox)

        hbox = gtk.HBox()
        hbox.pack_start( apply_button, False )
        hbox.pack_start( reset_button, False )
        hbox.pack_end( cancel_button, False )
        #hbox.pack_end( help_button, False )
        vbox.pack_start( hbox )

        window.add( vbox )
        window.show_all()

    def filter_reset( self, w):
        self.t.filter_include = None
        self.t.filter_exclude = None
        self.t.state_filter = None
        self.t.action_required = True

    def filter( self, w, excl_e, incl_e, fbox ):
        excl = excl_e.get_text()
        incl = incl_e.get_text()
        if excl == '':
            excl = None
        if incl == '':
            incl == None
        for filt in excl, incl:
            if not filt:
                continue
            try:
                re.compile( filt )
            except:
                warning_dialog( "Bad Expression: " + filt ).warn()
        self.t.filter_include = incl
        self.t.filter_exclude = excl

        fstates = []
        for b in fbox.get_children():
            if not b.get_active():
                # sub '_' from button label keyboard mnemonics
                fstates.append( re.sub('_', '', b.get_label()))
        if len(fstates) > 0:
            self.t.state_filter = fstates
        else:
            self.t.state_filter = None
        
        self.t.action_required = True

    def focused_timezoom_popup( self, w, id ):
        window = gtk.Window()
        window.modify_bg( gtk.STATE_NORMAL, 
                gtk.gdk.color_parse( self.log_colors.get_color()))
        window.set_border_width(5)
        window.set_title( "Cycle-Time Zoom")
        parent_window = self.xdot.widget.get_toplevel()
        if isinstance(parent_window, gtk.Window):
            window.set_transient_for( parent_window )
            window.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG )
        vbox = gtk.VBox()

        name, ctime = id.split(TaskID.DELIM)
        # TODO - do we need to check that oldeset_ctime is defined yet?
        cti = ct(ctime)
        octi = ct( self.t.oldest_ctime )
        ncti = ct( self.t.newest_ctime )
        diff_pre = cti.subtract_hrs( octi )
        diff_post = ncti.subtract_hrs( cti )

        # TODO - error checking on date range given
        box = gtk.HBox()
        label = gtk.Label( 'Pre (hours)' )
        box.pack_start( label, True )
        start_entry = gtk.Entry()
        start_entry.set_text(str(diff_pre))
        box.pack_start (start_entry, True)
        vbox.pack_start( box )

        box = gtk.HBox()
        label = gtk.Label( 'Post (hours)' )
        box.pack_start( label, True )
        stop_entry = gtk.Entry()
        stop_entry.set_text(str(diff_post))
        box.pack_start (stop_entry, True)
        vbox.pack_start( box )

        cancel_button = gtk.Button( "_Close" )
        cancel_button.connect("clicked", lambda x: window.destroy() )

        reset_button = gtk.Button( "_Reset (No Zoom)" )
        reset_button.connect("clicked", self.focused_timezoom_direct, None )

        apply_button = gtk.Button( "_Apply" )
        apply_button.connect("clicked", self.focused_timezoom, 
               ctime, start_entry, stop_entry )

        hbox = gtk.HBox()
        hbox.pack_start( apply_button, False )
        hbox.pack_start( reset_button, False )
        hbox.pack_end( cancel_button, False )
        #hbox.pack_end( help_button, False )
        vbox.pack_start( hbox )

        window.add( vbox )
        window.show_all()

    def focused_timezoom_direct( self, w, ctime ):
        self.t.focus_start_ctime = ctime
        self.t.focus_stop_ctime = ctime
        self.t.action_required = True
        self.t.best_fit = True

    def graph_timezoom_popup( self, w ):
        window = gtk.Window()
        window.modify_bg( gtk.STATE_NORMAL, 
                gtk.gdk.color_parse( self.log_colors.get_color()))
        window.set_border_width(5)
        window.set_title( "Time Zoom")
        parent_window = self.xdot.widget.get_toplevel()
        if isinstance(parent_window, gtk.Window):
            window.set_transient_for( parent_window )
            window.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG )
        vbox = gtk.VBox()

        # TODO - error checking on date range given
        box = gtk.HBox()
        label = gtk.Label( 'Start (YYYY[MM[DD[HH[mm[ss]]]]])' )
        box.pack_start( label, True )
        start_entry = gtk.Entry()
        start_entry.set_max_length(14)
        if self.t.oldest_ctime:
            start_entry.set_text(self.t.oldest_ctime)
        box.pack_start (start_entry, True)
        vbox.pack_start( box )

        box = gtk.HBox()
        label = gtk.Label( 'Stop (YYYY[MM[DD[HH[mm[ss]]]]])' )
        box.pack_start( label, True )
        stop_entry = gtk.Entry()
        stop_entry.set_max_length(14)
        if self.t.newest_ctime:
            stop_entry.set_text(self.t.newest_ctime)
        box.pack_start (stop_entry, True)
        vbox.pack_start( box )

        cancel_button = gtk.Button( "_Close" )
        cancel_button.connect("clicked", lambda x: window.destroy() )

        reset_button = gtk.Button( "_Reset (No Zoom)" )
        reset_button.connect("clicked", self.focused_timezoom_direct, None )

        apply_button = gtk.Button( "_Apply" )
        apply_button.connect("clicked", self.graph_timezoom, 
                start_entry, stop_entry )

        hbox = gtk.HBox()
        hbox.pack_start( apply_button, False )
        hbox.pack_start( reset_button, False )
        hbox.pack_end( cancel_button, False )
        #hbox.pack_end( help_button, False )
        vbox.pack_start( hbox )

        window.add( vbox )
        window.show_all()

    def graph_timezoom(self, w, start_e, stop_e):
        self.t.focus_start_ctime = start_e.get_text()
        self.t.focus_stop_ctime = stop_e.get_text()
        self.t.best_fit = True
        self.t.action_required = True

    def focused_timezoom(self, w, focus_ctime, start_e, stop_e):
        pre_hours = start_e.get_text()
        post_hours = stop_e.get_text()
        foo = ct(focus_ctime)
        foo.decrement( hours=pre_hours )
        self.t.focus_start_ctime = foo.get()
        bar = ct(focus_ctime)
        bar.increment( hours=post_hours )
        self.t.focus_stop_ctime = bar.get()
        self.t.best_fit = True
        self.t.action_required = True