class Roby::GUI::RelationsCanvas

Constants

COLORS
DISPLAY_POLICIES

Attributes

arrows[R]

A [object, object, relation] => GraphicsItem mapping of arrows

current_color[R]
current_time[R]
display_policy[R]
enabled_relations[R]

The set of relations that should be displayed

flashing_objects[R]

A set of events that are shown during only two calls of update

free_arrows[R]
graphics[R]

A DRbObject => GraphicsItem mapping

hide_finalized[RW]

True if the finalized tasks should not be displayed

keep_signals[RW]
last_arrows[R]

A [object, object, relation] => GraphicsItem mapping of arrows

layout_options[R]

@return [Hash] set of options that should be passed to

Graphviz#layout
plans[R]

The set of plans that should be displayed

relation_brushes[R]
relation_colors[R]
relation_pens[R]
scene[R]

The Qt::GraphicsScene we are manipulating

selected_objects[R]

The set of objects that are selected for display in the :explicit display mode

signal_arrows[R]

A pool of arrows items used to display the event signalling

visible_objects[R]

The set of objects that are to be shown at the last update

Public Class Methods

new(plans) click to toggle source
Calls superclass method
# File lib/roby/gui/relations_view/relations_canvas.rb, line 460
def initialize(plans)
    @scene  = Qt::GraphicsScene.new
    super()

    @plans  = plans.dup
    @display_plan_bounding_boxes = false

    @display_policy    = :explicit
    @graphics          = Hash.new
    @selected_objects   = Set.new
    @visible_objects   = Set.new
    @flashing_objects  = Hash.new
    @arrows            = Hash.new
    @free_arrows       = Array.new
    @enabled_relations = Set.new
    @layout_relations  = Set.new
    @relation_colors   = Hash.new
    @relation_pens     = Hash.new(Qt::Pen.new(Qt::Color.new(ARROW_COLOR)))
    @relation_brushes  = Hash.new(Qt::Brush.new(Qt::Color.new(ARROW_COLOR)))
    @current_color     = 0

    @signal_arrows     = []
    @hide_finalized    = true
    @layout_options    = Hash.new

    default_colors = {
        Roby::TaskStructure::Dependency => 'grey',
        Roby::TaskStructure::PlannedBy => '#32ba21',
        Roby::TaskStructure::ExecutionAgent => '#5d95cf',
        Roby::TaskStructure::ErrorHandling => '#ff2727'
    }
    default_colors.each do |rel, color|
        update_relation_color(rel, color)
    end

    relation_pens[Roby::EventStructure::Signal]    = Qt::Pen.new(Qt::Color.new('black'))
    relation_brushes[Roby::EventStructure::Signal] = Qt::Brush.new(Qt::Color.new('black'))
    relation_pens[Roby::EventStructure::Forwarding]    = Qt::Pen.new(Qt::Color.new('black'))
    relation_pens[Roby::EventStructure::Forwarding].style = Qt::DotLine
    relation_brushes[Roby::EventStructure::Forwarding] = Qt::Brush.new(Qt::Color.new('black'))
    relation_brushes[Roby::EventStructure::Forwarding].style = Qt::DotLine


    enable_relation(Roby::TaskStructure::Dependency)
    enable_relation(Roby::TaskStructure::ExecutionAgent)
    enable_relation(Roby::TaskStructure::PlannedBy)
end

Public Instance Methods

[](item) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 561
def [](item); graphics[item] end
add_flashing_object(object, &block) click to toggle source

Add object to the list of objects temporarily displayed. If a block is given, the object is removed when the block returns false. Otherwise, it is removed at the next display update

If this method is called more than once for the same object, the object is removed when all blocks have returned false at least once

# File lib/roby/gui/relations_view/relations_canvas.rb, line 773
def add_flashing_object(object, &block)
    if block
        flashing_objects[object] ||= []
        flashing_objects[object] << block
    else
        flashing_objects[object] ||= nil
    end
    if object.display_parent
        add_flashing_object(object.display_parent, &block)
    end

    create_or_get_item(object, false)
end
allocate_color() click to toggle source

returns the next color in COLORS, cycles if at the end of the array

# File lib/roby/gui/relations_view/relations_canvas.rb, line 633
def allocate_color
    @current_color = (current_color + 1) % COLORS.size
    COLORS[current_color]
end
apply_options(options) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 519
def apply_options(options)
    if enabled_relations = options['enabled_relations']
        enabled_relations.each do |name|
            rel = constant(name)
            enable_relation(rel)
        end
    end
    apply_simple_option('show_ownership', options)
    apply_simple_option('removed_prefixes', options)
    apply_simple_option('hide_finalized', options)
    apply_simple_option('removed_prefixes', options)
    apply_simple_option('hidden_labels', options)
    apply_simple_option('display_policy', options)
end
apply_simple_option(option_name, options) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 534
def apply_simple_option(option_name, options)
    if options.has_key?(option_name)
        self.send("#{option_name}=", options[option_name])
    end
end
arrow(from, to, rel, info, base_layer) click to toggle source

Creates or reuses an arrow object to represent the given relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 573
def arrow(from, to, rel, info, base_layer)
    id = [from, to, rel]
    if !(item = arrows[id])
        if item = last_arrows.delete(id)
            arrows[id] = item
        else
            item = arrows[id] = (free_arrows.pop || scene.add_arrow(ARROW_SIZE))
            item.z_value      = base_layer - 1
            item.pen   = item.line.pen = relation_pens[rel]
            item.brush = relation_brushes[rel]
        end
    end

    GUI.arrow_set item, self[from], self[to]
end
clear() click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 1068
def clear
    arrows.dup.each_value(&method(:remove_graphics))
    graphics.dup.each_value(&method(:remove_graphics))
    arrows.clear
    free_arrows.clear
    last_arrows.clear
    graphics.clear

    signal_arrows.each do |arrow|
        arrow.visible = false
    end

    selected_objects.clear
    visible_objects.clear
    flashing_objects.clear
    scene.update(scene.scene_rect)
end
clear_arrows(object) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 1059
def clear_arrows(object)
    arrows.delete_if do |(from, to, _), arrow|
        if from == object || to == object
            remove_graphics(arrow)
            true
        end
    end
end
clear_flashing_objects() click to toggle source

Removes the objects added with add_flashing_object when they should be removed

# File lib/roby/gui/relations_view/relations_canvas.rb, line 789
def clear_flashing_objects
    removed_objects = []
    flashing_objects.delete_if do |object, blocks|
        blocks.delete_if { |block| !block.call }
        if !blocks.empty?
            next
        end
        removed_objects << object
    end

    removed_objects.each do |object|
        if item = graphics[object]
            item.visible = displayed?(object)
        end
    end
end
create_or_get_item(object, initial_selection) { |item| ... } click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 748
def create_or_get_item(object, initial_selection)
    if !(item = graphics[object])
        item = graphics[object] = object.display_create(self)
        if item
            if object.display_parent
                item.parent_item = self[object.display_parent]
            end

            yield(item) if block_given?

            if initial_selection
                selected_objects << object
            end
        end
    end
    item
end
disable_relation(relation) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 673
def disable_relation(relation)
    return unless relation_enabled?(relation)
    @enabled_relations.delete(relation)
    arrows.each do |(_, _, rel), arrow|
        if rel == relation
            arrow.visible = false 
        end
    end
end
display_policy=(policy) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 734
def display_policy=(policy)
    if !DISPLAY_POLICIES.include?(policy)
        raise ArgumentError, "got #{policy.inspect} as a display policy, accepted values are #{DISPLAY_POLICIES.map(&:inspect).join(", ")}"
    end
    @display_policy = policy
end
displayed?(object) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 741
def displayed?(object)
    if (parent = object.display_parent) && !displayed?(parent)
        return false
    end
    return visible_objects.include?(object)
end
enable_relation(relation) click to toggle source

Display this relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 646
def enable_relation(relation)
    return if relation_enabled?(relation)
    @enabled_relations << relation
    arrows.each do |(_, _, rel), arrow|
        if rel == relation
            arrow.visible = true 
        end
    end
end
event_relation(form, to, rel, info) click to toggle source

Returns a canvas object that represents this relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 568
def event_relation(form, to, rel, info)
    arrow(from, to, rel, info, EVENT_LAYER)
end
find(regex = nil) click to toggle source

Centers the view on the set of object found which matches regex. If regex is nil, ask one to the user

# File lib/roby/gui/relations_view/relations_canvas.rb, line 591
def find(regex = nil)
    unless regex
        regex = Qt::InputDialog.get_text main, 'Find objects in relation view', 'Object name'
        return unless regex && !regex.empty?
    end
    regex = /#{regex.to_str}/i if regex.respond_to?(:to_str)

    # Get the tasks and events matching the string
    objects = []
    for p in plans
        objects.concat p.tasks.
            find_all { |object| displayed?(object) && regex === object.display_name(self) }
        objects.concat p.free_events.
            find_all { |object| displayed?(object) && regex === object.display_name(self) }
    end

    return if objects.empty?

    # Find the graphics items
    bb = objects.inject(Qt::RectF.new) do |bb, object| 
        if item = self[object]
            item.selected = true
            bb | item.scene_bounding_rect | item.map_to_scene(item.children_bounding_rect).bounding_rect
        else
            bb
        end
    end
    bb.adjust(-FIND_MARGIN, -FIND_MARGIN, FIND_MARGIN, FIND_MARGIN)
    ui.graphics.fit_in_view bb, Qt::KeepAspectRatio
    scale = ui.graphics.matrix.m11
    if scale > 1
        ui.graphics.resetMatrix
        ui.graphics.scale 1, 1
    end
end
ignore_relation(relation) click to toggle source

Don't use this relation at all

# File lib/roby/gui/relations_view/relations_canvas.rb, line 668
def ignore_relation(relation)
    disable_relation(relation)
    @layout_relations.delete(relation)
end
layout_method() click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 727
def layout_method
    return @layout_method if @layout_method
    "dot"
end
layout_method=(new_method) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 705
def layout_method=(new_method)
    return if new_method == @layout_method

    @layout_method  = nil
    @layout_options = nil
    if new_method
        new_method =~ /^(\w+)(?: \[(.*)\])?$/
        @layout_method    = $1
        if $2
            @layout_options = $2.split(",").inject(Hash.new) do |h, v|
                k, v = v.split("=")
                h[k] = v
                h
            end
        end
    end
    display
end
layout_relation(relation) click to toggle source

Use this relation for layout but not for display

See also ignore_relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 662
def layout_relation(relation)
    disable_relation(relation)
    @layout_relations << relation
end
layout_relation?(relation) click to toggle source

True if this relation should be used for layout

See also relation_enabled?, layout_relation, ignore_relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 643
def layout_relation?(relation); relation_enabled?(relation) || @layout_relations.include?(relation) end
make_graphics_visible(object) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 887
def make_graphics_visible(object)
    object = create_or_get_item(object, false)
    object.visible = true
    object
end
object_of(item) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 540
def object_of(item)
    id = item.data(0).to_string
    return if !id
    id = Integer(id)

    obj, _ = graphics.find do |obj, obj_item| 
        obj.object_id == id
    end
    obj
end
propagation_style(arrow, flag) click to toggle source

Sets the style on arrow according to the event propagation type provided in flag

arrow is the graphics item representing the relation and flag is one of the PROPAG_ constant

# File lib/roby/gui/relations_view/relations_canvas.rb, line 811
def propagation_style(arrow, flag)
    unless defined? @@propagation_styles
        @@propagation_styles = Hash.new
        @@propagation_styles[true] = 
            [Qt::Brush.new(Qt::Color.new('black')), Qt::Pen.new, (forward_pen = Qt::Pen.new)]
        forward_pen.style = Qt::DotLine
        @@propagation_styles[false] = 
            [Qt::Brush.new(Qt::Color.new('black')), Qt::Pen.new, Qt::Pen.new]
    end
    arrow.brush, arrow.pen, arrow.line.pen = @@propagation_styles[flag]
end
relation_color(relation) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 686
def relation_color(relation)
    if !relation_colors.has_key?(relation)
        update_relation_color(relation, allocate_color)
    end
    relation_colors[relation]
end
relation_enabled?(relation) click to toggle source

True if this relation should be displayed

# File lib/roby/gui/relations_view/relations_canvas.rb, line 639
def relation_enabled?(relation); @enabled_relations.include?(relation) end
relation_of(item) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 551
def relation_of(item)
    id = item.data(0).to_string
    arrows.each do |(from, to, rel), arrow|
        if arrow.data(0).to_string == id
            return from, to, rel
        end
    end
    nil
end
remove_graphics(item, scene = nil) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 1053
def remove_graphics(item, scene = nil)
    return unless item
    scene ||= item.scene
    scene.remove_item(item) if scene
end
save_options() click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 508
def save_options
    options = Hash.new
    options['enabled_relations'] = @enabled_relations.map(&:name)
    options['show_ownership'] = show_ownership
    options['hide_finalized'] = hide_finalized
    options['removed_prefixes'] = removed_prefixes.dup
    options['hidden_labels'] = hidden_labels.dup
    options['display_policy'] = display_policy
    options
end
task_relation(from, to, rel, info) click to toggle source

Returns a canvas object that represents this relation

# File lib/roby/gui/relations_view/relations_canvas.rb, line 564
def task_relation(from, to, rel, info)
    arrow(from, to, rel, info, TASK_LAYER)
end
update(time = nil) click to toggle source

Update the display with new data that has come from the data stream.

It would be too complex at this stage to know if the plan has been updated, so the method always returns true

# File lib/roby/gui/relations_view/relations_canvas.rb, line 900
def update(time = nil)
    # Allow time to be a Qt::DateTime object, so that we can make it
    # a slot
    if time.kind_of?(Qt::DateTime)
        time = Time.at(Float(time.toMSecsSinceEpoch) / 1000)
    end
    enabled_relations << Roby::EventStructure::Signal << Roby::EventStructure::Forwarding

    if time
        @current_time = time
    end

    @last_arrows, @arrows = arrows, Hash.new
    @free_arrows ||= Array.new

    update_prefixes_removal
    clear_flashing_objects

    # The sets of tasks and events know to the data stream
    all_tasks  = plans.inject(Set.new) do |all_tasks, plan|
        all_tasks.merge plan.tasks
        all_tasks.merge plan.finalized_tasks
    end
    all_events = plans.inject(Set.new) do |all_events, plan|
        all_events.merge plan.free_events
        all_events.merge plan.finalized_events
    end
    all_task_events = all_tasks.inject(Set.new) do |all_task_events, task|
        all_task_events.merge(task.bound_events.values)
    end

    # Remove the items for objects that don't exist anymore
    (graphics.keys.to_set - all_tasks - all_events - all_task_events).each do |obj|
        selected_objects.delete(obj)
        remove_graphics(graphics.delete(obj))
        clear_arrows(obj)
    end

    # Create graphics items for all objects that may get displayed
    # on the canvas
    all_tasks.each do |object|
        create_or_get_item(object, true)
        object.each_event do |ev|
            create_or_get_item(ev, false)
        end
    end
    all_events.each { |ev| create_or_get_item(ev, true) }
    plans.each { |p| create_or_get_item(p, display_plan_bounding_boxes?) }

    update_visible_objects

    graphics.each do |object, item|
        item.visible = displayed?(object)
    end

    RelationsCanvasEventGenerator.priorities.clear
    event_priority = 0
    plans.each do |p|
        flags = Hash.new(0)

        p.called_generators.each_with_index do |generator, priority|
            flags[generator] |= EVENT_CALLED
        end
        base_priority = p.called_generators.size

        p.emitted_events.each_with_index do |event, priority|
            generator = event.generator
            flags[generator] |= EVENT_EMITTED
        end

        p.failed_emissions.each do |generator, reason|
            flags[generator] = FAILED_EMISSION
        end

        flags.each_with_index do |(generator, generator_flags), priority|
            RelationsCanvasEventGenerator.priorities[generator] = priority
            if displayed?(generator)
                item = graphics[generator]
                item.brush, item.pen = RelationsCanvasEventGenerator.style(
                    generator, generator_flags)
            end
        end
    end
    
    plans.each do |p|
        p.propagated_events.each do |_, sources, to, _|
            sources.each do |from|
                RelationsCanvasEventGenerator.priorities[from] ||= (event_priority += 1)
                RelationsCanvasEventGenerator.priorities[to] ||= (event_priority += 1)
            end
        end
    end

    [all_tasks, all_events, plans].each do |object_set|
        object_set.each do |object|
            graphics = self.graphics[object]
            if graphics.visible?
                object.display(self, graphics)
            end
        end
    end

    # Update arrow visibility
    arrows.each do |(from, to, rel), item|
        next if !@enabled_relations.include?(rel)
        item.visible = (displayed?(from) && displayed?(to))
    end

    # Layout the graph
    layouts = plans.find_all { |p| p.root_plan? }.
        map do |p| 
            dot = PlanDotLayout.new
            begin
                dot.layout(self, p, layout_options)
                dot
            rescue Exception => e
                puts "Failed to lay out the plan: #{e}"
            end
        end.compact
    layouts.each { |dot| dot.apply }

    # Display the signals
    signal_arrow_idx = -1
    plans.each do |p|
        p.propagated_events.each_with_index do |(flag, sources, to), signal_arrow_idx|
            relation =
                if flag
                    Roby::EventStructure::Forwarding
                else
                    Roby::EventStructure::Signal
                end

            sources.each do |source_event|
                arrow = arrow(source_event.generator, to, relation, nil, EVENT_PROPAGATION_LAYER)
                propagation_style(arrow, flag)
            end
        end
    end
    arrows.each do |_, item|
        item.visible = true
    end
    @free_arrows = last_arrows.values
    free_arrows.each do |item|
        item.visible = false
    end
    last_arrows.clear

    true
#rescue Exception => e
#    message = "<html>#{e.message.gsub('<', '&lt;').gsub('>', '&gt;')}<ul><li>#{e.backtrace.join("</li><li>")}</li></ul></html>"
#    Qt::MessageBox.critical nil, "Display failure", message
end
update_relation_color(relation, color) click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 692
def update_relation_color(relation, color)
    relation_colors[relation] = color
    color = Qt::Color.new(color)
    pen   = relation_pens[relation]    = Qt::Pen.new(color)
    brush = relation_brushes[relation] = Qt::Brush.new(color)
    arrows.each do |(_, _, rel), arrow|
        if rel == relation
            arrow.pen = arrow.line.pen = pen
            arrow.brush = brush
        end
    end
end
update_visible_objects() click to toggle source
# File lib/roby/gui/relations_view/relations_canvas.rb, line 823
def update_visible_objects
    @visible_objects = Set.new

    # NOTE: we unconditionally add events that are propagated, as
    # #displayed?(obj) will filter out the ones whose task is hidden
    plans.each do |p|
        if display_plan_bounding_boxes?
            visible_objects << p
        end
        p.emitted_events.each do |event|
            visible_objects << event.generator
        end
        p.propagated_events.each do |_, sources, to, _|
            sources.each do |src|
                visible_objects << src.generator
            end
            visible_objects << to
        end
    end

    if display_policy == :explicit
        visible_objects.merge(selected_objects)

    elsif display_policy == :emitters || display_policy == :emitters_and_parents
        # Make sure that the event's tasks are added to
        # visible_objects as well
        visible_objects.dup.each do |obj|
            if parent = obj.display_parent
                visible_objects << parent
            end
        end
    end

    if display_policy == :emitters_and_parents
        while true
            new_visible_objects = Set.new
            visible_objects.group_by(&:plan).each do |plan, plan_objects|
                graphs = plan.each_task_relation_graph.find_all(&:root_relation?).map(&:reverse)
                new_visible_objects.merge(plan.compute_useful_tasks(plan_objects.to_set, graphs: graphs))
                new_visible_objects.subtract(plan_objects.to_set)
            end
            break if new_visible_objects.empty?
            visible_objects.merge(new_visible_objects)
        end
        visible_objects.dup.each do |obj|
            if obj.kind_of?(Roby::Task)
                obj.each_relation do |rel|
                    visible_objects.merge(obj.child_objects(rel))
                end
            end
        end
    end

    if hide_finalized
        plans.each do |plan|
            all_finalized = plan.finalized_tasks | plan.finalized_events
            @visible_objects = visible_objects - all_finalized
        end
    end
    visible_objects.delete_if do |obj|
        filtered_out_label?(obj.display_name(self))
    end
end