<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Traversals over the DOM and flow trees, running the layout computations.

#![allow(unsafe_blocks)]

use css::node_style::StyledNode;
use css::matching::{ApplicableDeclarations, MatchMethods, StyleSharingResult};
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{Flow, MutableFlowUtils};
use flow::{PreorderFlowTraversal, PostorderFlowTraversal};
use flow;
use incremental::{RestyleDamage, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW};
use wrapper::{layout_node_to_unsafe_layout_node, LayoutNode};
use wrapper::{PostorderNodeMutTraversal, ThreadSafeLayoutNode, UnsafeLayoutNode};
use wrapper::{PreorderDomTraversal, PostorderDomTraversal};

use servo_util::bloom::BloomFilter;
use servo_util::opts;
use servo_util::tid::tid;
use style::node::TNode;

use std::cell::RefCell;
use std::mem;

/// Every time we do another layout, the old bloom filters are invalid. This is
/// detected by ticking a generation number every layout.
type Generation = uint;

/// A pair of the bloom filter used for css selector matching, and the node to
/// which it applies. This is used to efficiently do `Descendant` selector
/// matches. Thanks to the bloom filter, we can avoid walking up the tree
/// looking for ancestors that aren't there in the majority of cases.
///
/// As we walk down the DOM tree a task-local bloom filter is built of all the
/// CSS `SimpleSelector`s which are part of a `Descendant` compound selector
/// (i.e. paired with a `Descendant` combinator, in the `next` field of a
/// `CompoundSelector`.
///
/// Before a `Descendant` selector match is tried, it's compared against the
/// bloom filter. If the bloom filter can exclude it, the selector is quickly
/// rejected.
///
/// When done styling a node, all selectors previously inserted into the filter
/// are removed.
///
/// Since a work-stealing queue is used for styling, sometimes, the bloom filter
/// will no longer be the for the parent of the node we're currently on. When
/// this happens, the task local bloom filter will be thrown away and rebuilt.
thread_local!(static STYLE_BLOOM: RefCell<Option<(Box<BloomFilter>, UnsafeLayoutNode, Generation)>> = RefCell::new(None));

/// Returns the task local bloom filter.
///
/// If one does not exist, a new one will be made for you. If it is out of date,
/// it will be thrown out and a new one will be made for you.
fn take_task_local_bloom_filter(parent_node: Option<LayoutNode>, layout_context: &LayoutContext)
                                -> Box<BloomFilter> {
    STYLE_BLOOM.with(|style_bloom| {
        match (parent_node, style_bloom.borrow_mut().take()) {
            // Root node. Needs new bloom filter.
            (None,     _  ) => {
                debug!("[{}] No parent, but new bloom filter!", tid());
                box BloomFilter::new()
            }
            // No bloom filter for this thread yet.
            (Some(parent), None) => {
                let mut bloom_filter = box BloomFilter::new();
                insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, layout_context);
                bloom_filter
            }
            // Found cached bloom filter.
            (Some(parent), Some((mut bloom_filter, old_node, old_generation))) => {
                // Hey, the cached parent is our parent! We can reuse the bloom filter.
                if old_node == layout_node_to_unsafe_layout_node(&parent) &&
                    old_generation == layout_context.shared.generation {
                    debug!("[{}] Parent matches (={}). Reusing bloom filter.", tid(), old_node.0);
                    bloom_filter.clone()
                } else {
                    // Oh no. the cached parent is stale. I guess we need a new one. Reuse the existing
                    // allocation to avoid malloc churn.
                    *bloom_filter = BloomFilter::new();
                    insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, layout_context);
                    bloom_filter
                }
            },
        }
    })
}

fn put_task_local_bloom_filter(bf: Box<BloomFilter>,
                               unsafe_node: &UnsafeLayoutNode,
                               layout_context: &LayoutContext) {
    STYLE_BLOOM.with(move |style_bloom| {
        assert!(style_bloom.borrow().is_none(),
                "Putting into a never-taken task-local bloom filter");
        *style_bloom.borrow_mut() = Some((bf, *unsafe_node, layout_context.shared.generation));
    })
}

/// "Ancestors" in this context is inclusive of ourselves.
fn insert_ancestors_into_bloom_filter(bf: &mut Box<BloomFilter>,
                                      mut n: LayoutNode,
                                      layout_context: &LayoutContext) {
    debug!("[{}] Inserting ancestors.", tid());
    let mut ancestors = 0u;
    loop {
        ancestors += 1;

        n.insert_into_bloom_filter(&mut **bf);
        n = match n.layout_parent_node(layout_context.shared) {
            None => break,
            Some(p) => p,
        };
    }
    debug!("[{}] Inserted {} ancestors.", tid(), ancestors);
}

/// The recalc-style-for-node traversal, which styles each node and must run before
/// layout computation. This computes the styles applied to each node.
#[derive(Copy)]
pub struct RecalcStyleForNode<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
    #[inline]
    fn process(&self, node: LayoutNode) {
        // Initialize layout data.
        //
        // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
        // parser.
        node.initialize_layout_data(self.layout_context.shared.layout_chan.clone());

        // Get the parent node.
        let parent_opt = node.layout_parent_node(self.layout_context.shared);

        // Get the style bloom filter.
        let bf = take_task_local_bloom_filter(parent_opt, self.layout_context);

        // Just needs to be wrapped in an option for `match_node`.
        let some_bf = Some(bf);

        let nonincremental_layout = opts::get().nonincremental_layout;
        if nonincremental_layout || node.is_dirty() {
            // Remove existing CSS styles from nodes whose content has changed (e.g. text changed),
            // to force non-incremental reflow.
            if node.has_changed() {
                let node = ThreadSafeLayoutNode::new(&node);
                node.unstyle();
            }

            // Check to see whether we can share a style with someone.
            let style_sharing_candidate_cache =
                self.layout_context.style_sharing_candidate_cache();
            let sharing_result = unsafe {
                node.share_style_if_possible(style_sharing_candidate_cache,
                                             parent_opt.clone())
            };
            // Otherwise, match and cascade selectors.
            match sharing_result {
                StyleSharingResult::CannotShare(mut shareable) => {
                    let mut applicable_declarations = ApplicableDeclarations::new();

                    if node.is_element() {
                        // Perform the CSS selector matching.
                        let stylist = unsafe { &*self.layout_context.shared.stylist };
                        node.match_node(stylist,
                                        &some_bf,
                                        &mut applicable_declarations,
                                        &mut shareable);
                    } else {
                        ThreadSafeLayoutNode::new(&node).set_restyle_damage(RestyleDamage::all())
                    }

                    // Perform the CSS cascade.
                    unsafe {
                        node.cascade_node(parent_opt,
                                          &applicable_declarations,
                                          self.layout_context.applicable_declarations_cache());
                    }

                    // Add ourselves to the LRU cache.
                    if shareable {
                        style_sharing_candidate_cache.insert_if_possible(&node);
                    }
                }
                StyleSharingResult::StyleWasShared(index, damage) => {
                    style_sharing_candidate_cache.touch(index);
                    ThreadSafeLayoutNode::new(&node).set_restyle_damage(damage);
                }
            }
        }

        let mut bf = some_bf.unwrap();

        let unsafe_layout_node = layout_node_to_unsafe_layout_node(&node);

        // Before running the children, we need to insert our nodes into the bloom
        // filter.
        debug!("[{}] + {:X}", tid(), unsafe_layout_node.0);
        node.insert_into_bloom_filter(&mut *bf);

        // NB: flow construction updates the bloom filter on the way up.
        put_task_local_bloom_filter(bf, &unsafe_layout_node, self.layout_context);
    }
}

/// The flow construction traversal, which builds flows for styled nodes.
#[derive(Copy)]
pub struct ConstructFlows<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderDomTraversal for ConstructFlows<'a> {
    #[inline]
    fn process(&self, node: LayoutNode) {
        // Construct flows for this node.
        {
            let tnode = ThreadSafeLayoutNode::new(&node);

            // Always reconstruct if incremental layout is turned off.
            let nonincremental_layout = opts::get().nonincremental_layout;
            if nonincremental_layout || node.has_dirty_descendants() {
                let mut flow_constructor = FlowConstructor::new(self.layout_context);
                if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
                    flow_constructor.process(&tnode);
                    debug!("Constructed flow for {:x}: {:x}",
                           tnode.debug_id(),
                           tnode.flow_debug_id());
                }
            }

            // Reset the layout damage in this node. It's been propagated to the
            // flow by the flow constructor.
            tnode.set_restyle_damage(RestyleDamage::empty());
        }

        unsafe {
            node.set_changed(false);
            node.set_dirty(false);
            node.set_dirty_siblings(false);
            node.set_dirty_descendants(false);
        }

        let unsafe_layout_node = layout_node_to_unsafe_layout_node(&node);

        let (mut bf, old_node, old_generation) =
            STYLE_BLOOM.with(|style_bloom| {
                mem::replace(&mut *style_bloom.borrow_mut(), None)
                .expect("The bloom filter should have been set by style recalc.")
            });

        assert_eq!(old_node, unsafe_layout_node);
        assert_eq!(old_generation, self.layout_context.shared.generation);

        match node.layout_parent_node(self.layout_context.shared) {
            None => {
                debug!("[{}] - {:X}, and deleting BF.", tid(), unsafe_layout_node.0);
                // If this is the reflow root, eat the task-local bloom filter.
            }
            Some(parent) => {
                // Otherwise, put it back, but remove this node.
                node.remove_from_bloom_filter(&mut *bf);
                let unsafe_parent = layout_node_to_unsafe_layout_node(&parent);
                put_task_local_bloom_filter(bf, &unsafe_parent, self.layout_context);
            },
        };
    }
}

/// The flow tree verification traversal. This is only on in debug builds.
#[cfg(debug)]
struct FlowTreeVerification;

#[cfg(debug)]
impl PreorderFlow for FlowTreeVerification {
    #[inline]
    fn process(&mut self, flow: &mut Flow) {
        let base = flow::base(flow);
        if !base.flags.is_leaf() && !base.flags.is_nonleaf() {
            println!("flow tree verification failed: flow wasn't a leaf or a nonleaf!");
            flow.dump();
            panic!("flow tree verification failed")
        }
    }
}

/// The bubble-inline-sizes traversal, the first part of layout computation. This computes
/// preferred and intrinsic inline-sizes and bubbles them up the tree.
pub struct BubbleISizes<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for BubbleISizes<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.bubble_inline_sizes();
        flow::mut_base(flow).restyle_damage.remove(BUBBLE_ISIZES);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.contains(BUBBLE_ISIZES)
    }
}

/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
#[derive(Copy)]
pub struct AssignISizes<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.assign_inline_sizes(self.layout_context);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW)
    }
}

/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of
/// layout computation. Determines the final block-sizes for all layout objects, computes
/// positions, and computes overflow regions. In Gecko this corresponds to `Reflow` and
/// `FinishAndStoreOverflow`.
#[derive(Copy)]
pub struct AssignBSizesAndStoreOverflow<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflow<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        // Can't do anything with flows impacted by floats until we reach their inorder parent.
        // NB: We must return without resetting the restyle bits for these, as we haven't actually
        // reflowed anything!
        if flow::base(flow).flags.impacted_by_floats() {
            return
        }

        flow.assign_block_size(self.layout_context);
        flow.store_overflow(self.layout_context);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW)
    }
}

#[derive(Copy)]
pub struct ComputeAbsolutePositions<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderFlowTraversal for ComputeAbsolutePositions<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.compute_absolute_position();
    }
}

#[derive(Copy)]
pub struct BuildDisplayList<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for BuildDisplayList<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.build_display_list(self.layout_context);
    }
}
<commit_msg>auto merge of #4938 : nnethercote/servo/dont-clone-bloom-filter, r=Ms2ger<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Traversals over the DOM and flow trees, running the layout computations.

#![allow(unsafe_blocks)]

use css::node_style::StyledNode;
use css::matching::{ApplicableDeclarations, MatchMethods, StyleSharingResult};
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{Flow, MutableFlowUtils};
use flow::{PreorderFlowTraversal, PostorderFlowTraversal};
use flow;
use incremental::{RestyleDamage, BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW};
use wrapper::{layout_node_to_unsafe_layout_node, LayoutNode};
use wrapper::{PostorderNodeMutTraversal, ThreadSafeLayoutNode, UnsafeLayoutNode};
use wrapper::{PreorderDomTraversal, PostorderDomTraversal};

use servo_util::bloom::BloomFilter;
use servo_util::opts;
use servo_util::tid::tid;
use style::node::TNode;

use std::cell::RefCell;
use std::mem;

/// Every time we do another layout, the old bloom filters are invalid. This is
/// detected by ticking a generation number every layout.
type Generation = uint;

/// A pair of the bloom filter used for css selector matching, and the node to
/// which it applies. This is used to efficiently do `Descendant` selector
/// matches. Thanks to the bloom filter, we can avoid walking up the tree
/// looking for ancestors that aren't there in the majority of cases.
///
/// As we walk down the DOM tree a task-local bloom filter is built of all the
/// CSS `SimpleSelector`s which are part of a `Descendant` compound selector
/// (i.e. paired with a `Descendant` combinator, in the `next` field of a
/// `CompoundSelector`.
///
/// Before a `Descendant` selector match is tried, it's compared against the
/// bloom filter. If the bloom filter can exclude it, the selector is quickly
/// rejected.
///
/// When done styling a node, all selectors previously inserted into the filter
/// are removed.
///
/// Since a work-stealing queue is used for styling, sometimes, the bloom filter
/// will no longer be the for the parent of the node we're currently on. When
/// this happens, the task local bloom filter will be thrown away and rebuilt.
thread_local!(static STYLE_BLOOM: RefCell<Option<(Box<BloomFilter>, UnsafeLayoutNode, Generation)>> = RefCell::new(None));

/// Returns the task local bloom filter.
///
/// If one does not exist, a new one will be made for you. If it is out of date,
/// it will be cleared and reused.
fn take_task_local_bloom_filter(parent_node: Option<LayoutNode>, layout_context: &LayoutContext)
                                -> Box<BloomFilter> {
    STYLE_BLOOM.with(|style_bloom| {
        match (parent_node, style_bloom.borrow_mut().take()) {
            // Root node. Needs new bloom filter.
            (None,     _  ) => {
                debug!("[{}] No parent, but new bloom filter!", tid());
                box BloomFilter::new()
            }
            // No bloom filter for this thread yet.
            (Some(parent), None) => {
                let mut bloom_filter = box BloomFilter::new();
                insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, layout_context);
                bloom_filter
            }
            // Found cached bloom filter.
            (Some(parent), Some((mut bloom_filter, old_node, old_generation))) => {
                if old_node == layout_node_to_unsafe_layout_node(&parent) &&
                    old_generation == layout_context.shared.generation {
                    // Hey, the cached parent is our parent! We can reuse the bloom filter.
                    debug!("[{}] Parent matches (={}). Reusing bloom filter.", tid(), old_node.0);
                } else {
                    // Oh no. the cached parent is stale. I guess we need a new one. Reuse the existing
                    // allocation to avoid malloc churn.
                    bloom_filter.clear();
                    insert_ancestors_into_bloom_filter(&mut bloom_filter, parent, layout_context);
                }
                bloom_filter
            },
        }
    })
}

fn put_task_local_bloom_filter(bf: Box<BloomFilter>,
                               unsafe_node: &UnsafeLayoutNode,
                               layout_context: &LayoutContext) {
    STYLE_BLOOM.with(move |style_bloom| {
        assert!(style_bloom.borrow().is_none(),
                "Putting into a never-taken task-local bloom filter");
        *style_bloom.borrow_mut() = Some((bf, *unsafe_node, layout_context.shared.generation));
    })
}

/// "Ancestors" in this context is inclusive of ourselves.
fn insert_ancestors_into_bloom_filter(bf: &mut Box<BloomFilter>,
                                      mut n: LayoutNode,
                                      layout_context: &LayoutContext) {
    debug!("[{}] Inserting ancestors.", tid());
    let mut ancestors = 0u;
    loop {
        ancestors += 1;

        n.insert_into_bloom_filter(&mut **bf);
        n = match n.layout_parent_node(layout_context.shared) {
            None => break,
            Some(p) => p,
        };
    }
    debug!("[{}] Inserted {} ancestors.", tid(), ancestors);
}

/// The recalc-style-for-node traversal, which styles each node and must run before
/// layout computation. This computes the styles applied to each node.
#[derive(Copy)]
pub struct RecalcStyleForNode<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderDomTraversal for RecalcStyleForNode<'a> {
    #[inline]
    fn process(&self, node: LayoutNode) {
        // Initialize layout data.
        //
        // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
        // parser.
        node.initialize_layout_data(self.layout_context.shared.layout_chan.clone());

        // Get the parent node.
        let parent_opt = node.layout_parent_node(self.layout_context.shared);

        // Get the style bloom filter.
        let bf = take_task_local_bloom_filter(parent_opt, self.layout_context);

        // Just needs to be wrapped in an option for `match_node`.
        let some_bf = Some(bf);

        let nonincremental_layout = opts::get().nonincremental_layout;
        if nonincremental_layout || node.is_dirty() {
            // Remove existing CSS styles from nodes whose content has changed (e.g. text changed),
            // to force non-incremental reflow.
            if node.has_changed() {
                let node = ThreadSafeLayoutNode::new(&node);
                node.unstyle();
            }

            // Check to see whether we can share a style with someone.
            let style_sharing_candidate_cache =
                self.layout_context.style_sharing_candidate_cache();
            let sharing_result = unsafe {
                node.share_style_if_possible(style_sharing_candidate_cache,
                                             parent_opt.clone())
            };
            // Otherwise, match and cascade selectors.
            match sharing_result {
                StyleSharingResult::CannotShare(mut shareable) => {
                    let mut applicable_declarations = ApplicableDeclarations::new();

                    if node.is_element() {
                        // Perform the CSS selector matching.
                        let stylist = unsafe { &*self.layout_context.shared.stylist };
                        node.match_node(stylist,
                                        &some_bf,
                                        &mut applicable_declarations,
                                        &mut shareable);
                    } else {
                        ThreadSafeLayoutNode::new(&node).set_restyle_damage(RestyleDamage::all())
                    }

                    // Perform the CSS cascade.
                    unsafe {
                        node.cascade_node(parent_opt,
                                          &applicable_declarations,
                                          self.layout_context.applicable_declarations_cache());
                    }

                    // Add ourselves to the LRU cache.
                    if shareable {
                        style_sharing_candidate_cache.insert_if_possible(&node);
                    }
                }
                StyleSharingResult::StyleWasShared(index, damage) => {
                    style_sharing_candidate_cache.touch(index);
                    ThreadSafeLayoutNode::new(&node).set_restyle_damage(damage);
                }
            }
        }

        let mut bf = some_bf.unwrap();

        let unsafe_layout_node = layout_node_to_unsafe_layout_node(&node);

        // Before running the children, we need to insert our nodes into the bloom
        // filter.
        debug!("[{}] + {:X}", tid(), unsafe_layout_node.0);
        node.insert_into_bloom_filter(&mut *bf);

        // NB: flow construction updates the bloom filter on the way up.
        put_task_local_bloom_filter(bf, &unsafe_layout_node, self.layout_context);
    }
}

/// The flow construction traversal, which builds flows for styled nodes.
#[derive(Copy)]
pub struct ConstructFlows<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderDomTraversal for ConstructFlows<'a> {
    #[inline]
    fn process(&self, node: LayoutNode) {
        // Construct flows for this node.
        {
            let tnode = ThreadSafeLayoutNode::new(&node);

            // Always reconstruct if incremental layout is turned off.
            let nonincremental_layout = opts::get().nonincremental_layout;
            if nonincremental_layout || node.has_dirty_descendants() {
                let mut flow_constructor = FlowConstructor::new(self.layout_context);
                if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
                    flow_constructor.process(&tnode);
                    debug!("Constructed flow for {:x}: {:x}",
                           tnode.debug_id(),
                           tnode.flow_debug_id());
                }
            }

            // Reset the layout damage in this node. It's been propagated to the
            // flow by the flow constructor.
            tnode.set_restyle_damage(RestyleDamage::empty());
        }

        unsafe {
            node.set_changed(false);
            node.set_dirty(false);
            node.set_dirty_siblings(false);
            node.set_dirty_descendants(false);
        }

        let unsafe_layout_node = layout_node_to_unsafe_layout_node(&node);

        let (mut bf, old_node, old_generation) =
            STYLE_BLOOM.with(|style_bloom| {
                mem::replace(&mut *style_bloom.borrow_mut(), None)
                .expect("The bloom filter should have been set by style recalc.")
            });

        assert_eq!(old_node, unsafe_layout_node);
        assert_eq!(old_generation, self.layout_context.shared.generation);

        match node.layout_parent_node(self.layout_context.shared) {
            None => {
                debug!("[{}] - {:X}, and deleting BF.", tid(), unsafe_layout_node.0);
                // If this is the reflow root, eat the task-local bloom filter.
            }
            Some(parent) => {
                // Otherwise, put it back, but remove this node.
                node.remove_from_bloom_filter(&mut *bf);
                let unsafe_parent = layout_node_to_unsafe_layout_node(&parent);
                put_task_local_bloom_filter(bf, &unsafe_parent, self.layout_context);
            },
        };
    }
}

/// The flow tree verification traversal. This is only on in debug builds.
#[cfg(debug)]
struct FlowTreeVerification;

#[cfg(debug)]
impl PreorderFlow for FlowTreeVerification {
    #[inline]
    fn process(&mut self, flow: &mut Flow) {
        let base = flow::base(flow);
        if !base.flags.is_leaf() && !base.flags.is_nonleaf() {
            println!("flow tree verification failed: flow wasn't a leaf or a nonleaf!");
            flow.dump();
            panic!("flow tree verification failed")
        }
    }
}

/// The bubble-inline-sizes traversal, the first part of layout computation. This computes
/// preferred and intrinsic inline-sizes and bubbles them up the tree.
pub struct BubbleISizes<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for BubbleISizes<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.bubble_inline_sizes();
        flow::mut_base(flow).restyle_damage.remove(BUBBLE_ISIZES);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.contains(BUBBLE_ISIZES)
    }
}

/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
#[derive(Copy)]
pub struct AssignISizes<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderFlowTraversal for AssignISizes<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.assign_inline_sizes(self.layout_context);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW)
    }
}

/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of
/// layout computation. Determines the final block-sizes for all layout objects, computes
/// positions, and computes overflow regions. In Gecko this corresponds to `Reflow` and
/// `FinishAndStoreOverflow`.
#[derive(Copy)]
pub struct AssignBSizesAndStoreOverflow<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflow<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        // Can't do anything with flows impacted by floats until we reach their inorder parent.
        // NB: We must return without resetting the restyle bits for these, as we haven't actually
        // reflowed anything!
        if flow::base(flow).flags.impacted_by_floats() {
            return
        }

        flow.assign_block_size(self.layout_context);
        flow.store_overflow(self.layout_context);
    }

    #[inline]
    fn should_process(&self, flow: &mut Flow) -> bool {
        flow::base(flow).restyle_damage.intersects(REFLOW_OUT_OF_FLOW | REFLOW)
    }
}

#[derive(Copy)]
pub struct ComputeAbsolutePositions<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PreorderFlowTraversal for ComputeAbsolutePositions<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.compute_absolute_position();
    }
}

#[derive(Copy)]
pub struct BuildDisplayList<'a> {
    pub layout_context: &'a LayoutContext<'a>,
}

impl<'a> PostorderFlowTraversal for BuildDisplayList<'a> {
    #[inline]
    fn process(&self, flow: &mut Flow) {
        flow.build_display_list(self.layout_context);
    }
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::DocumentBinding::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::root::DomRoot;
use dom::document::Document;
use dom::element::Element;
use dom::htmlelement::HTMLElement;
use dom::node::Node;
use std::borrow::Cow;
use std::collections::HashMap;

pub struct Microdata {}

impl Microdata {
    //[Pref="dom.microdata.testing.enabled"]
    pub fn parse(doc: &Document) -> String {

        let ele = doc.upcast::<Node>();
        let mut start_vcard = false;
        let mut result : String = String::new();
        let mut master_map : HashMap<String, HashMap<String, String>> = HashMap::new();
        let mut master_key : String = String::new();

        result += "BEGIN:VCARD\nPROFILE:VCARD\nVERSION:4.0\nSOURCE:";
        result += doc.url().as_str();

        let title = doc.Title();
        if !title.is_empty() && !title.trim().is_empty() {
            result += "\nNAME:";
            result += title.trim();
        }

        result += "\n";
        let mut sex : String = String::new();
        let mut gender_identity : String = String::new();

        for element in ele.traverse_preorder().filter_map(DomRoot::downcast::<Element>){
            if element.is::<HTMLElement>() {
                if element.has_attribute(&local_name!("itemtype")){
                    let mut atoms = element.get_tokenlist_attribute(&local_name!("itemtype"), );
                    if !atoms.is_empty() {
                        let val = atoms.remove(0);
                        if val.trim() == "http://microformats.org/profile/hcard"{
                            if !start_vcard {
                                start_vcard = true;
                            } else {
                                break;
                            }
                        }
                    }
                }
                if start_vcard {
                    let mut atoms = element.get_tokenlist_attribute(&local_name!("itemprop"), );
                    if !atoms.is_empty() {
                        let temp_key = atoms.remove(0);
                        if element.has_attribute(&local_name!("itemscope")){
                            master_key = String::from(temp_key.trim()).to_owned();
                            let dup_master_key = Cow::Borrowed(&master_key);
                            master_map.entry(dup_master_key.to_string()).or_insert(HashMap::new());
                        } else {
                            let temp = String::from(temp_key.trim()).to_owned();
                            let dup_key = Cow::Borrowed(&temp);
                            let data = String::from(element.GetInnerHTML().unwrap());
                            let dup_master_key = Cow::Borrowed(&master_key);
                            let temp_map = master_map.entry(dup_master_key.to_string()).or_insert(HashMap::new());
                            temp_map.insert(dup_key.to_string(), String::from(data));
                        }
                    }
                }
            }
        }
        //println!("{:?}", master_map);
        for (info_type, detail_map) in &master_map {
            //println!("{} -> {:?}", info_type, detail_map);
            match info_type.as_str() {
                "n" => {

                    let mut n_value : String = String::new();

                    let name_parts = ["family-name", "given-name", "additional-name", "honorific-prefix", "honorific-suffix"];
                    for part in name_parts.iter() {
                        if detail_map.contains_key(*part) {
                            n_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    n_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), n_value).as_str();

                },
                "org" => {

                    let mut org_value : String = String::new();

                    let org_parts = ["organization-name", "organization-unit"];
                    for part in org_parts.iter() {
                        if detail_map.contains_key(*part) {
                            org_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    org_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), org_value).as_str();

                },
                "tel" => {

                },
                "adr" => {

                    let mut adr_value : String = String::new();

                    let adr_parts = ["street-address", "locality", "region", "postal-code", "country-name", "post-office-box", "extended-address"];
                    for part in adr_parts.iter() {
                        if detail_map.contains_key(*part) {
                            adr_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    adr_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), adr_value).as_str();

                },
                _ => {},
            }
        }

        result += "END:VCARD";
        println!("{}", result);
        return result;
    }
}
<commit_msg>Adding adr to vCard<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::DocumentBinding::DocumentBinding::DocumentMethods;
use dom::bindings::codegen::Bindings::ElementBinding::ElementBinding::ElementMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::root::DomRoot;
use dom::document::Document;
use dom::element::Element;
use dom::htmlelement::HTMLElement;
use dom::node::Node;
use std::borrow::Cow;
use std::collections::HashMap;

pub struct Microdata {}

impl Microdata {
    //[Pref="dom.microdata.testing.enabled"]
    pub fn parse(doc: &Document) -> String {

        let ele = doc.upcast::<Node>();
        let mut start_vcard = false;
        let mut result : String = String::new();
        let mut master_map : HashMap<String, HashMap<String, String>> = HashMap::new();
        let mut master_key : String = String::new();

        result += "BEGIN:VCARD\nPROFILE:VCARD\nVERSION:4.0\nSOURCE:";
        result += doc.url().as_str();

        let title = doc.Title();
        if !title.is_empty() && !title.trim().is_empty() {
            result += "\nNAME:";
            result += title.trim();
        }

        result += "\n";

        for element in ele.traverse_preorder().filter_map(DomRoot::downcast::<Element>){
            if element.is::<HTMLElement>() {
                if element.has_attribute(&local_name!("itemtype")){
                    let mut atoms = element.get_tokenlist_attribute(&local_name!("itemtype"), );
                    if !atoms.is_empty() {
                        let val = atoms.remove(0);
                        if val.trim() == "http://microformats.org/profile/hcard"{
                            if !start_vcard {
                                start_vcard = true;
                            } else {
                                break;
                            }
                        }
                    }
                }
                if start_vcard {
                    let mut atoms = element.get_tokenlist_attribute(&local_name!("itemprop"), );
                    if !atoms.is_empty() {
                        let temp_key = atoms.remove(0);
                        if element.has_attribute(&local_name!("itemscope")){
                            master_key = String::from(temp_key.trim()).to_owned();
                            let dup_master_key = Cow::Borrowed(&master_key);
                            master_map.entry(dup_master_key.to_string()).or_insert(HashMap::new());
                        } else {
                            let temp = String::from(temp_key.trim()).to_owned();
                            let dup_key = Cow::Borrowed(&temp);
                            let data = String::from(element.GetInnerHTML().unwrap());
                            let dup_master_key = Cow::Borrowed(&master_key);
                            let temp_map = master_map.entry(dup_master_key.to_string()).or_insert(HashMap::new());
                            temp_map.insert(dup_key.to_string(), String::from(data));
                        }
                    }
                }
            }
        }

        for (info_type, detail_map) in &master_map {

            match info_type.as_str() {
                "n" => {

                    let mut n_value : String = String::new();

                    let name_parts = ["family-name", "given-name", "additional-name", "honorific-prefix", "honorific-suffix"];
                    for part in name_parts.iter() {
                        if detail_map.contains_key(*part) {
                            n_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    n_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), n_value).as_str();

                },
                "org" => {

                    let mut org_value : String = String::new();

                    let org_parts = ["organization-name", "organization-unit"];
                    for part in org_parts.iter() {
                        if detail_map.contains_key(*part) {
                            org_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    org_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), org_value).as_str();

                },
                "tel" => {

                },
                "adr" => {

                    let mut adr_value : String = String::new();

                    let adr_parts = ["street-address", "locality", "region", "postal-code", "country-name", "post-office-box", "extended-address"];
                    for part in adr_parts.iter() {
                        if detail_map.contains_key(*part) {
                            adr_value += format!("{};",detail_map.get(*part).unwrap()).as_str();
                        }
                    }
                    adr_value.pop();

                    result += format!("{}:{}\n", info_type.as_str(), adr_value).as_str();

                },
                _ => {},
            }
        }

        result += "END:VCARD";
        println!("{}", result);
        return result;
    }
}
<|endoftext|>


<commit_before>use stats::LogProb;

pub mod evidence {
    /// Scale of evidence as defined by
    /// [Kass and Raftery 1995](http://www.andrew.cmu.edu/user/kk3n/simplicity/KassRaftery1995.pdf).
    #[derive(Display, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString, EnumIter, IntoStaticStr)]
    #[strum(serialize_all = "snake_case")]
    pub enum KassRaftery {
        None,
        Barely,
        Positive,
        Strong,
        #[strum(serialize = "very-strong")]
        VeryStrong,
    }
}

custom_derive! {
    /// A newtype for Bayes factors.
    #[derive(
        NewtypeFrom,
        NewtypeDeref,
        PartialEq,
        PartialOrd,
        Copy,
        Clone,
        Debug,
    )]
    pub struct BayesFactor(pub f64);
}

impl BayesFactor {
    /// Calculate Bayes factor from given probabilities.
    pub fn new(a: LogProb, b: LogProb) -> Self {
        BayesFactor((a - b).exp())
    }

    /// Calculate strength of evidence as defined by
    /// [Kass and Raftery 1995](http://www.andrew.cmu.edu/user/kk3n/simplicity/KassRaftery1995.pdf).
    pub fn evidence_kass_raftery(&self) -> evidence::KassRaftery {
        let k = **self;
        if k >= 1.0 && k <= 3.0 {
            evidence::KassRaftery::Barely
        } else if k <= 20.0 {
            evidence::KassRaftery::Positive
        } else if k <= 150.0 {
            evidence::KassRaftery::Strong
        } else {
            evidence::KassRaftery::VeryStrong
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bayes_factor() {
        let bf = BayesFactor::new(LogProb(0.5_f64.ln()), LogProb(0.1_f64.ln()));
        assert_relative_eq!(*bf, 5.0, epsilon = 1e-9);
        assert_eq!(bf.evidence_kass_raftery(), evidence::KassRaftery::Positive);
    }
}
<commit_msg>explicitly serialize<commit_after>use stats::LogProb;

pub mod evidence {
    /// Scale of evidence as defined by
    /// [Kass and Raftery 1995](http://www.andrew.cmu.edu/user/kk3n/simplicity/KassRaftery1995.pdf).
    #[derive(Display, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString, EnumIter, IntoStaticStr)]
    pub enum KassRaftery {
        #[strum(serialize = "none")]
        None,
        #[strum(serialize = "barely")]
        Barely,
        #[strum(serialize = "positive")]
        Positive,
        #[strum(serialize = "strong")]
        Strong,
        #[strum(serialize = "very-strong")]
        VeryStrong,
    }
}

custom_derive! {
    /// A newtype for Bayes factors.
    #[derive(
        NewtypeFrom,
        NewtypeDeref,
        PartialEq,
        PartialOrd,
        Copy,
        Clone,
        Debug,
    )]
    pub struct BayesFactor(pub f64);
}

impl BayesFactor {
    /// Calculate Bayes factor from given probabilities.
    pub fn new(a: LogProb, b: LogProb) -> Self {
        BayesFactor((a - b).exp())
    }

    /// Calculate strength of evidence as defined by
    /// [Kass and Raftery 1995](http://www.andrew.cmu.edu/user/kk3n/simplicity/KassRaftery1995.pdf).
    pub fn evidence_kass_raftery(&self) -> evidence::KassRaftery {
        let k = **self;
        if k >= 1.0 && k <= 3.0 {
            evidence::KassRaftery::Barely
        } else if k <= 20.0 {
            evidence::KassRaftery::Positive
        } else if k <= 150.0 {
            evidence::KassRaftery::Strong
        } else {
            evidence::KassRaftery::VeryStrong
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bayes_factor() {
        let bf = BayesFactor::new(LogProb(0.5_f64.ln()), LogProb(0.1_f64.ln()));
        assert_relative_eq!(*bf, 5.0, epsilon = 1e-9);
        assert_eq!(bf.evidence_kass_raftery(), evidence::KassRaftery::Positive);
    }
}
<|endoftext|>


<commit_before>use runtime::Runtime;

/**
 * A function which lets the user provide content by editing a temp files which gets removed after
 * the function got the content from it.
 */
pub fn let_user_provide_content(rt: &Runtime) -> Option<String> {
    use std::io::Read;
    use std::fs::File;
    use std::process::Command;

    let filepath        = "/tmp/imag-tmp.md";
    let file_created    = File::create(filepath)
                                .map(|_| true)
                                .unwrap_or(false);

    if !file_created {
        warn!("Could not create temporary file for user input!");
        return None;
    }

    let output = {
        let mut cmd = rt.editor();
        cmd.arg(filepath);
        debug!("cmd = {:?}", cmd);
        cmd.spawn()
           .and_then(|child| {
               child.wait_with_output()
            })
    };

    let process_out = output.map_err(|e| {
        error!("Editor call failed");
        debug!("Editor call failed: {:?}", e);
        return None as Option<String>;
    }).unwrap();

    if !process_out.status.success() {
        error!("Editor call failed");
        debug!("status = {:?}", process_out.status);
        debug!("stdout = {:?}", String::from_utf8(process_out.stdout));
        debug!("stderr = {:?}", String::from_utf8(process_out.stderr));
        return None;
    }

    let mut contents = String::new();
    File::open(filepath).map(|mut file| {
        file.read_to_string(&mut contents);
        Some(contents)
    }).unwrap_or(None)
}

/**
 * Edit some content in a temporary file. If anything failes within this routine, it returns the
 * old content and false.
 * If the editing succeeded, it returns the new content and true
 */
pub fn edit_content(rt: &Runtime, old_content: String) -> (String, bool) {
    use std::io::Read;
    use std::io::Write;
    use std::fs::File;
    use std::process::Command;
    use std::process::exit;

    let filepath = "/tmp/imag-tmp.md";
    {
        let mut file = match File::create(filepath) {
            Ok(f) => f,
            Err(e) => {
                error!("Error creating file {}", filepath);
                debug!("Error creating file at '{}', error = {}", filepath, e);
                exit(1);
            }
        };

        file.write(old_content.as_ref());
    }
    debug!("Ready with putting old content into the file");

    let output = {
        let mut cmd = rt.editor();
        cmd.arg(filepath);
        debug!("cmd = {:?}", cmd);
        cmd.spawn()
           .and_then(|child| child.wait_with_output())
    };

    let process_out = output.map_err(|e| {
        error!("Editor call failed");
        debug!("Editor call failed: {:?}", e);
        return None as Option<String>;
    }).unwrap();

    if !process_out.status.success() {
        error!("Editor call failed");
        debug!("status = {:?}", process_out.status);
        debug!("stdout = {:?}", String::from_utf8(process_out.stdout));
        debug!("stderr = {:?}", String::from_utf8(process_out.stderr));
        return (old_content, false);
    }

    let mut contents = String::new();
    File::open(filepath).map(|mut file| {
        file.read_to_string(&mut contents);
        (contents, true)
    }).unwrap_or((old_content, false))
}

<commit_msg>Use return value: fail if content could not be read<commit_after>use runtime::Runtime;

/**
 * A function which lets the user provide content by editing a temp files which gets removed after
 * the function got the content from it.
 */
pub fn let_user_provide_content(rt: &Runtime) -> Option<String> {
    use std::io::Read;
    use std::fs::File;
    use std::process::Command;

    let filepath        = "/tmp/imag-tmp.md";
    let file_created    = File::create(filepath)
                                .map(|_| true)
                                .unwrap_or(false);

    if !file_created {
        warn!("Could not create temporary file for user input!");
        return None;
    }

    let output = {
        let mut cmd = rt.editor();
        cmd.arg(filepath);
        debug!("cmd = {:?}", cmd);
        cmd.spawn()
           .and_then(|child| {
               child.wait_with_output()
            })
    };

    let process_out = output.map_err(|e| {
        error!("Editor call failed");
        debug!("Editor call failed: {:?}", e);
        return None as Option<String>;
    }).unwrap();

    if !process_out.status.success() {
        error!("Editor call failed");
        debug!("status = {:?}", process_out.status);
        debug!("stdout = {:?}", String::from_utf8(process_out.stdout));
        debug!("stderr = {:?}", String::from_utf8(process_out.stderr));
        return None;
    }

    let mut contents = String::new();
    File::open(filepath).map(|mut file| {
        file.read_to_string(&mut contents);
        Some(contents)
    }).unwrap_or(None)
}

/**
 * Edit some content in a temporary file. If anything failes within this routine, it returns the
 * old content and false.
 * If the editing succeeded, it returns the new content and true
 */
pub fn edit_content(rt: &Runtime, old_content: String) -> (String, bool) {
    use std::io::Read;
    use std::io::Write;
    use std::fs::File;
    use std::process::Command;
    use std::process::exit;

    let filepath = "/tmp/imag-tmp.md";
    {
        let mut file = match File::create(filepath) {
            Ok(f) => f,
            Err(e) => {
                error!("Error creating file {}", filepath);
                debug!("Error creating file at '{}', error = {}", filepath, e);
                exit(1);
            }
        };

        file.write(old_content.as_ref());
    }
    debug!("Ready with putting old content into the file");

    let output = {
        let mut cmd = rt.editor();
        cmd.arg(filepath);
        debug!("cmd = {:?}", cmd);
        cmd.spawn()
           .and_then(|child| child.wait_with_output())
    };

    let process_out = output.map_err(|e| {
        error!("Editor call failed");
        debug!("Editor call failed: {:?}", e);
        return None as Option<String>;
    }).unwrap();

    if !process_out.status.success() {
        error!("Editor call failed");
        debug!("status = {:?}", process_out.status);
        debug!("stdout = {:?}", String::from_utf8(process_out.stdout));
        debug!("stderr = {:?}", String::from_utf8(process_out.stderr));
        return (old_content, false);
    }

    let mut contents = String::new();
    File::open(filepath).map(|mut file| {
        file.read_to_string(&mut contents).map_err(|e| {
            error!("Error reading content: {}", e);
            debug!("Error reading content: {:?}", e);
            exit(1);
        }).is_ok();
        (contents, true)
    }).unwrap_or((old_content, false))
}

<|endoftext|>


<commit_before>use cairo;
use gettextrs::gettext;
use gstreamer as gst;
use gtk;
use gtk::prelude::*;
use lazy_static::lazy_static;
use log::{info, warn};

use std::{cell::RefCell, fs::File, rc::Rc};

use crate::application::{CommandLineArguments, CONFIG};

use media::Timestamp;
use metadata;
use metadata::{Duration, MediaInfo, Timestamp4Humans};
use renderers::Image;

use super::{
    ChapterTreeManager, ChaptersBoundaries, ControllerState, PlaybackPipeline, PositionStatus,
    UIController, UIEventSender,
};

lazy_static! {
    static ref EMPTY_REPLACEMENT: &'static str = "-";
}

pub struct InfoController {
    ui_event: UIEventSender,

    pub(super) info_container: gtk::Grid,
    pub(super) show_chapters_btn: gtk::ToggleButton,

    pub(super) drawingarea: gtk::DrawingArea,

    title_lbl: gtk::Label,
    artist_lbl: gtk::Label,
    container_lbl: gtk::Label,
    audio_codec_lbl: gtk::Label,
    video_codec_lbl: gtk::Label,
    duration_lbl: gtk::Label,

    pub(super) timeline_scale: gtk::Scale,
    pub(super) repeat_btn: gtk::ToggleToolButton,

    pub(super) chapter_treeview: gtk::TreeView,
    add_chapter_btn: gtk::ToolButton,
    del_chapter_btn: gtk::ToolButton,

    thumbnail: Option<Image>,

    pub(super) chapter_manager: ChapterTreeManager,

    duration: Duration,
    pub(super) repeat_chapter: bool,
}

impl UIController for InfoController {
    fn setup(&mut self, _args: &CommandLineArguments) {
        self.cleanup();

        // Show chapters toggle
        if CONFIG.read().unwrap().ui.is_chapters_list_hidden {
            self.show_chapters_btn.set_active(false);
            self.info_container.hide();
        }

        self.show_chapters_btn.set_sensitive(true);
    }

    fn new_media(&mut self, pipeline: &PlaybackPipeline) {
        let toc_extensions = metadata::Factory::get_extensions();

        {
            let info = pipeline.info.read().unwrap();

            // check the presence of a toc file
            let mut toc_candidates =
                toc_extensions
                    .into_iter()
                    .filter_map(|(extension, format)| {
                        let path = info
                            .path
                            .with_file_name(&format!("{}.{}", info.name, extension));
                        if path.is_file() {
                            Some((path, format))
                        } else {
                            None
                        }
                    });

            self.duration = info.duration;
            self.timeline_scale.set_range(0f64, info.duration.as_f64());
            self.duration_lbl
                .set_label(&Timestamp4Humans::from_duration(info.duration).to_string());

            self.thumbnail = info.get_media_image().and_then(|image| {
                image.get_buffer().and_then(|image_buffer| {
                    image_buffer.map_readable().and_then(|image_map| {
                        Image::from_unknown(image_map.as_slice())
                            .map_err(|err| warn!("{}", err))
                            .ok()
                    })
                })
            });

            self.container_lbl
                .set_label(info.get_container().unwrap_or(&EMPTY_REPLACEMENT));

            let extern_toc = toc_candidates
                .next()
                .and_then(|(toc_path, format)| match File::open(toc_path.clone()) {
                    Ok(mut toc_file) => {
                        match metadata::Factory::get_reader(format).read(&info, &mut toc_file) {
                            Ok(Some(toc)) => Some(toc),
                            Ok(None) => {
                                let msg = gettext("No toc in file \"{}\"").replacen(
                                    "{}",
                                    toc_path.file_name().unwrap().to_str().unwrap(),
                                    1,
                                );
                                info!("{}", msg);
                                self.ui_event.show_info(msg);
                                None
                            }
                            Err(err) => {
                                self.ui_event.show_error(
                                    gettext("Error opening toc file \"{}\":\n{}")
                                        .replacen(
                                            "{}",
                                            toc_path.file_name().unwrap().to_str().unwrap(),
                                            1,
                                        )
                                        .replacen("{}", &err, 1),
                                );
                                None
                            }
                        }
                    }
                    Err(_) => {
                        self.ui_event
                            .show_error(gettext("Failed to open toc file."));
                        None
                    }
                });

            if extern_toc.is_some() {
                self.chapter_manager.replace_with(&extern_toc);
            } else {
                self.chapter_manager.replace_with(&info.toc);
            }
        }

        self.update_marks();

        self.repeat_btn.set_sensitive(true);
        self.add_chapter_btn.set_sensitive(true);
        match self.chapter_manager.selected() {
            Some(sel_chapter) => {
                // position is in a chapter => select it
                self.chapter_treeview
                    .get_selection()
                    .select_iter(sel_chapter.iter());
                self.del_chapter_btn.set_sensitive(true);
            }
            None =>
            // position is not in any chapter
            {
                self.del_chapter_btn.set_sensitive(false)
            }
        }

        if self.thumbnail.is_some() {
            self.drawingarea.show();
            self.drawingarea.queue_draw();
        } else {
            self.drawingarea.hide();
        }
    }

    fn cleanup(&mut self) {
        self.title_lbl.set_text("");
        self.artist_lbl.set_text("");
        self.container_lbl.set_text("");
        self.audio_codec_lbl.set_text("");
        self.video_codec_lbl.set_text("");
        self.duration_lbl.set_text("00:00.000");
        self.thumbnail = None;
        self.chapter_manager.clear();
        self.add_chapter_btn.set_sensitive(false);
        self.del_chapter_btn.set_sensitive(false);
        self.timeline_scale.clear_marks();
        self.timeline_scale.set_value(0f64);
        self.duration = Duration::default();
    }

    fn streams_changed(&mut self, info: &MediaInfo) {
        match info.get_media_artist() {
            Some(artist) => self.artist_lbl.set_label(&artist),
            None => self.artist_lbl.set_label(&EMPTY_REPLACEMENT),
        }
        match info.get_media_title() {
            Some(title) => self.title_lbl.set_label(&title),
            None => self.title_lbl.set_label(&EMPTY_REPLACEMENT),
        }

        self.audio_codec_lbl
            .set_label(info.streams.get_audio_codec().unwrap_or(&EMPTY_REPLACEMENT));
        self.video_codec_lbl
            .set_label(info.streams.get_video_codec().unwrap_or(&EMPTY_REPLACEMENT));
    }
}

impl InfoController {
    pub fn new(
        builder: &gtk::Builder,
        ui_event_sender: UIEventSender,
        boundaries: Rc<RefCell<ChaptersBoundaries>>,
    ) -> Self {
        let mut chapter_manager = ChapterTreeManager::new(
            builder.get_object("chapters-tree-store").unwrap(),
            boundaries,
        );
        let chapter_treeview: gtk::TreeView = builder.get_object("chapter-treeview").unwrap();
        chapter_manager.init_treeview(&chapter_treeview);

        InfoController {
            ui_event: ui_event_sender,

            info_container: builder.get_object("info-chapter_list-grid").unwrap(),
            show_chapters_btn: builder.get_object("show_chapters-toggle").unwrap(),

            drawingarea: builder.get_object("thumbnail-drawingarea").unwrap(),

            title_lbl: builder.get_object("title-lbl").unwrap(),
            artist_lbl: builder.get_object("artist-lbl").unwrap(),
            container_lbl: builder.get_object("container-lbl").unwrap(),
            audio_codec_lbl: builder.get_object("audio_codec-lbl").unwrap(),
            video_codec_lbl: builder.get_object("video_codec-lbl").unwrap(),
            duration_lbl: builder.get_object("duration-lbl").unwrap(),

            timeline_scale: builder.get_object("timeline-scale").unwrap(),
            repeat_btn: builder.get_object("repeat-toolbutton").unwrap(),

            chapter_treeview,
            add_chapter_btn: builder.get_object("add_chapter-toolbutton").unwrap(),
            del_chapter_btn: builder.get_object("remove_chapter-toolbutton").unwrap(),

            thumbnail: None,

            chapter_manager,

            duration: Duration::default(),
            repeat_chapter: false,
        }
    }

    pub fn draw_thumbnail(&mut self, drawingarea: &gtk::DrawingArea, cairo_ctx: &cairo::Context) {
        if let Some(image) = self.thumbnail.as_mut() {
            let allocation = drawingarea.get_allocation();
            let alloc_width_f: f64 = allocation.width.into();
            let alloc_height_f: f64 = allocation.height.into();

            let image_width_f: f64 = image.width().into();
            let image_height_f: f64 = image.height().into();

            let alloc_ratio = alloc_width_f / alloc_height_f;
            let image_ratio = image_width_f / image_height_f;
            let scale = if image_ratio < alloc_ratio {
                alloc_height_f / image_height_f
            } else {
                alloc_width_f / image_width_f
            };
            let x = (alloc_width_f / scale - image_width_f).abs() / 2f64;
            let y = (alloc_height_f / scale - image_height_f).abs() / 2f64;

            image.with_surface_external_context(cairo_ctx, |cr, surface| {
                cr.scale(scale, scale);
                cr.set_source_surface(surface, x, y);
                cr.paint();
            })
        }
    }

    fn update_marks(&self) {
        self.timeline_scale.clear_marks();

        let timeline_scale = self.timeline_scale.clone();
        self.chapter_manager.iter().for_each(move |chapter| {
            timeline_scale.add_mark(chapter.start().as_f64(), gtk::PositionType::Top, None);
        });
    }

    fn repeat_at(&self, ts: Timestamp) {
        self.ui_event.seek(ts, gst::SeekFlags::ACCURATE)
    }

    pub fn tick(&mut self, ts: Timestamp, state: ControllerState) {
        self.timeline_scale.set_value(ts.as_f64());

        let mut position_status = self.chapter_manager.update_ts(ts);

        if self.repeat_chapter {
            // repeat is activated
            if state == ControllerState::EOS {
                // postpone chapter selection change until media has synchronized
                position_status = PositionStatus::ChapterNotChanged;
                self.repeat_at(Timestamp::default());
            } else if let PositionStatus::ChapterChanged { prev_chapter } = &position_status {
                if let Some(prev_chapter) = prev_chapter {
                    // reset position_status because we will be looping on current chapter
                    let prev_start = prev_chapter.start;
                    position_status = PositionStatus::ChapterNotChanged;

                    // unselect chapter in order to avoid tracing change to current timestamp
                    self.chapter_manager.unselect();
                    self.repeat_at(prev_start);
                }
            }
        }

        if let PositionStatus::ChapterChanged { prev_chapter } = position_status {
            // let go the mutable reference on `self.chapter_manager`
            match self.chapter_manager.selected() {
                Some(sel_chapter) => {
                    // timestamp is in a chapter => select it
                    self.chapter_treeview
                        .get_selection()
                        .select_iter(sel_chapter.iter());
                    self.del_chapter_btn.set_sensitive(true);
                }
                None =>
                // timestamp is not in any chapter
                {
                    if let Some(prev_chapter) = prev_chapter {
                        // but a previous chapter was selected => unselect it
                        self.chapter_treeview
                            .get_selection()
                            .unselect_iter(&prev_chapter.iter);
                        self.del_chapter_btn.set_sensitive(false);
                    }
                }
            }
        }
    }

    pub fn move_chapter_boundary(
        &mut self,
        boundary: Timestamp,
        target: Timestamp,
    ) -> PositionStatus {
        self.chapter_manager.move_chapter_boundary(boundary, target)
    }

    pub fn seek(&mut self, target: Timestamp) {
        self.tick(target, ControllerState::Seeking);
    }

    pub fn add_chapter(&mut self, ts: Timestamp) {
        if ts >= self.duration {
            // can't add a chapter starting at last position
            return;
        }

        if let Some(new_iter) = self.chapter_manager.add_chapter(ts, self.duration) {
            self.chapter_treeview.get_selection().select_iter(&new_iter);
            self.update_marks();
            self.del_chapter_btn.set_sensitive(true);
        }
    }

    pub fn remove_chapter(&mut self) {
        match self.chapter_manager.remove_selected_chapter() {
            Some(new_iter) => self.chapter_treeview.get_selection().select_iter(&new_iter),
            None => {
                self.chapter_treeview.get_selection().unselect_all();
                self.del_chapter_btn.set_sensitive(false);
            }
        }

        self.update_marks();
    }

    pub fn export_chapters(&self, info: &mut MediaInfo) {
        if let Some((toc, count)) = self.chapter_manager.get_toc() {
            info.toc = Some(toc);
            info.chapter_count = Some(count);
        }
    }
}
<commit_msg>Remove unneeded lazy_static<commit_after>use cairo;
use gettextrs::gettext;
use gstreamer as gst;
use gtk;
use gtk::prelude::*;
use log::{info, warn};

use std::{cell::RefCell, fs::File, rc::Rc};

use crate::application::{CommandLineArguments, CONFIG};

use media::Timestamp;
use metadata;
use metadata::{Duration, MediaInfo, Timestamp4Humans};
use renderers::Image;

use super::{
    ChapterTreeManager, ChaptersBoundaries, ControllerState, PlaybackPipeline, PositionStatus,
    UIController, UIEventSender,
};

const EMPTY_REPLACEMENT: &str = "-";

pub struct InfoController {
    ui_event: UIEventSender,

    pub(super) info_container: gtk::Grid,
    pub(super) show_chapters_btn: gtk::ToggleButton,

    pub(super) drawingarea: gtk::DrawingArea,

    title_lbl: gtk::Label,
    artist_lbl: gtk::Label,
    container_lbl: gtk::Label,
    audio_codec_lbl: gtk::Label,
    video_codec_lbl: gtk::Label,
    duration_lbl: gtk::Label,

    pub(super) timeline_scale: gtk::Scale,
    pub(super) repeat_btn: gtk::ToggleToolButton,

    pub(super) chapter_treeview: gtk::TreeView,
    add_chapter_btn: gtk::ToolButton,
    del_chapter_btn: gtk::ToolButton,

    thumbnail: Option<Image>,

    pub(super) chapter_manager: ChapterTreeManager,

    duration: Duration,
    pub(super) repeat_chapter: bool,
}

impl UIController for InfoController {
    fn setup(&mut self, _args: &CommandLineArguments) {
        self.cleanup();

        // Show chapters toggle
        if CONFIG.read().unwrap().ui.is_chapters_list_hidden {
            self.show_chapters_btn.set_active(false);
            self.info_container.hide();
        }

        self.show_chapters_btn.set_sensitive(true);
    }

    fn new_media(&mut self, pipeline: &PlaybackPipeline) {
        let toc_extensions = metadata::Factory::get_extensions();

        {
            let info = pipeline.info.read().unwrap();

            // check the presence of a toc file
            let mut toc_candidates =
                toc_extensions
                    .into_iter()
                    .filter_map(|(extension, format)| {
                        let path = info
                            .path
                            .with_file_name(&format!("{}.{}", info.name, extension));
                        if path.is_file() {
                            Some((path, format))
                        } else {
                            None
                        }
                    });

            self.duration = info.duration;
            self.timeline_scale.set_range(0f64, info.duration.as_f64());
            self.duration_lbl
                .set_label(&Timestamp4Humans::from_duration(info.duration).to_string());

            self.thumbnail = info.get_media_image().and_then(|image| {
                image.get_buffer().and_then(|image_buffer| {
                    image_buffer.map_readable().and_then(|image_map| {
                        Image::from_unknown(image_map.as_slice())
                            .map_err(|err| warn!("{}", err))
                            .ok()
                    })
                })
            });

            self.container_lbl
                .set_label(info.get_container().unwrap_or(EMPTY_REPLACEMENT));

            let extern_toc = toc_candidates
                .next()
                .and_then(|(toc_path, format)| match File::open(toc_path.clone()) {
                    Ok(mut toc_file) => {
                        match metadata::Factory::get_reader(format).read(&info, &mut toc_file) {
                            Ok(Some(toc)) => Some(toc),
                            Ok(None) => {
                                let msg = gettext("No toc in file \"{}\"").replacen(
                                    "{}",
                                    toc_path.file_name().unwrap().to_str().unwrap(),
                                    1,
                                );
                                info!("{}", msg);
                                self.ui_event.show_info(msg);
                                None
                            }
                            Err(err) => {
                                self.ui_event.show_error(
                                    gettext("Error opening toc file \"{}\":\n{}")
                                        .replacen(
                                            "{}",
                                            toc_path.file_name().unwrap().to_str().unwrap(),
                                            1,
                                        )
                                        .replacen("{}", &err, 1),
                                );
                                None
                            }
                        }
                    }
                    Err(_) => {
                        self.ui_event
                            .show_error(gettext("Failed to open toc file."));
                        None
                    }
                });

            if extern_toc.is_some() {
                self.chapter_manager.replace_with(&extern_toc);
            } else {
                self.chapter_manager.replace_with(&info.toc);
            }
        }

        self.update_marks();

        self.repeat_btn.set_sensitive(true);
        self.add_chapter_btn.set_sensitive(true);
        match self.chapter_manager.selected() {
            Some(sel_chapter) => {
                // position is in a chapter => select it
                self.chapter_treeview
                    .get_selection()
                    .select_iter(sel_chapter.iter());
                self.del_chapter_btn.set_sensitive(true);
            }
            None =>
            // position is not in any chapter
            {
                self.del_chapter_btn.set_sensitive(false)
            }
        }

        if self.thumbnail.is_some() {
            self.drawingarea.show();
            self.drawingarea.queue_draw();
        } else {
            self.drawingarea.hide();
        }
    }

    fn cleanup(&mut self) {
        self.title_lbl.set_text("");
        self.artist_lbl.set_text("");
        self.container_lbl.set_text("");
        self.audio_codec_lbl.set_text("");
        self.video_codec_lbl.set_text("");
        self.duration_lbl.set_text("00:00.000");
        self.thumbnail = None;
        self.chapter_manager.clear();
        self.add_chapter_btn.set_sensitive(false);
        self.del_chapter_btn.set_sensitive(false);
        self.timeline_scale.clear_marks();
        self.timeline_scale.set_value(0f64);
        self.duration = Duration::default();
    }

    fn streams_changed(&mut self, info: &MediaInfo) {
        match info.get_media_artist() {
            Some(artist) => self.artist_lbl.set_label(&artist),
            None => self.artist_lbl.set_label(EMPTY_REPLACEMENT),
        }
        match info.get_media_title() {
            Some(title) => self.title_lbl.set_label(&title),
            None => self.title_lbl.set_label(EMPTY_REPLACEMENT),
        }

        self.audio_codec_lbl
            .set_label(info.streams.get_audio_codec().unwrap_or(EMPTY_REPLACEMENT));
        self.video_codec_lbl
            .set_label(info.streams.get_video_codec().unwrap_or(EMPTY_REPLACEMENT));
    }
}

impl InfoController {
    pub fn new(
        builder: &gtk::Builder,
        ui_event_sender: UIEventSender,
        boundaries: Rc<RefCell<ChaptersBoundaries>>,
    ) -> Self {
        let mut chapter_manager = ChapterTreeManager::new(
            builder.get_object("chapters-tree-store").unwrap(),
            boundaries,
        );
        let chapter_treeview: gtk::TreeView = builder.get_object("chapter-treeview").unwrap();
        chapter_manager.init_treeview(&chapter_treeview);

        InfoController {
            ui_event: ui_event_sender,

            info_container: builder.get_object("info-chapter_list-grid").unwrap(),
            show_chapters_btn: builder.get_object("show_chapters-toggle").unwrap(),

            drawingarea: builder.get_object("thumbnail-drawingarea").unwrap(),

            title_lbl: builder.get_object("title-lbl").unwrap(),
            artist_lbl: builder.get_object("artist-lbl").unwrap(),
            container_lbl: builder.get_object("container-lbl").unwrap(),
            audio_codec_lbl: builder.get_object("audio_codec-lbl").unwrap(),
            video_codec_lbl: builder.get_object("video_codec-lbl").unwrap(),
            duration_lbl: builder.get_object("duration-lbl").unwrap(),

            timeline_scale: builder.get_object("timeline-scale").unwrap(),
            repeat_btn: builder.get_object("repeat-toolbutton").unwrap(),

            chapter_treeview,
            add_chapter_btn: builder.get_object("add_chapter-toolbutton").unwrap(),
            del_chapter_btn: builder.get_object("remove_chapter-toolbutton").unwrap(),

            thumbnail: None,

            chapter_manager,

            duration: Duration::default(),
            repeat_chapter: false,
        }
    }

    pub fn draw_thumbnail(&mut self, drawingarea: &gtk::DrawingArea, cairo_ctx: &cairo::Context) {
        if let Some(image) = self.thumbnail.as_mut() {
            let allocation = drawingarea.get_allocation();
            let alloc_width_f: f64 = allocation.width.into();
            let alloc_height_f: f64 = allocation.height.into();

            let image_width_f: f64 = image.width().into();
            let image_height_f: f64 = image.height().into();

            let alloc_ratio = alloc_width_f / alloc_height_f;
            let image_ratio = image_width_f / image_height_f;
            let scale = if image_ratio < alloc_ratio {
                alloc_height_f / image_height_f
            } else {
                alloc_width_f / image_width_f
            };
            let x = (alloc_width_f / scale - image_width_f).abs() / 2f64;
            let y = (alloc_height_f / scale - image_height_f).abs() / 2f64;

            image.with_surface_external_context(cairo_ctx, |cr, surface| {
                cr.scale(scale, scale);
                cr.set_source_surface(surface, x, y);
                cr.paint();
            })
        }
    }

    fn update_marks(&self) {
        self.timeline_scale.clear_marks();

        let timeline_scale = self.timeline_scale.clone();
        self.chapter_manager.iter().for_each(move |chapter| {
            timeline_scale.add_mark(chapter.start().as_f64(), gtk::PositionType::Top, None);
        });
    }

    fn repeat_at(&self, ts: Timestamp) {
        self.ui_event.seek(ts, gst::SeekFlags::ACCURATE)
    }

    pub fn tick(&mut self, ts: Timestamp, state: ControllerState) {
        self.timeline_scale.set_value(ts.as_f64());

        let mut position_status = self.chapter_manager.update_ts(ts);

        if self.repeat_chapter {
            // repeat is activated
            if state == ControllerState::EOS {
                // postpone chapter selection change until media has synchronized
                position_status = PositionStatus::ChapterNotChanged;
                self.repeat_at(Timestamp::default());
            } else if let PositionStatus::ChapterChanged { prev_chapter } = &position_status {
                if let Some(prev_chapter) = prev_chapter {
                    // reset position_status because we will be looping on current chapter
                    let prev_start = prev_chapter.start;
                    position_status = PositionStatus::ChapterNotChanged;

                    // unselect chapter in order to avoid tracing change to current timestamp
                    self.chapter_manager.unselect();
                    self.repeat_at(prev_start);
                }
            }
        }

        if let PositionStatus::ChapterChanged { prev_chapter } = position_status {
            // let go the mutable reference on `self.chapter_manager`
            match self.chapter_manager.selected() {
                Some(sel_chapter) => {
                    // timestamp is in a chapter => select it
                    self.chapter_treeview
                        .get_selection()
                        .select_iter(sel_chapter.iter());
                    self.del_chapter_btn.set_sensitive(true);
                }
                None =>
                // timestamp is not in any chapter
                {
                    if let Some(prev_chapter) = prev_chapter {
                        // but a previous chapter was selected => unselect it
                        self.chapter_treeview
                            .get_selection()
                            .unselect_iter(&prev_chapter.iter);
                        self.del_chapter_btn.set_sensitive(false);
                    }
                }
            }
        }
    }

    pub fn move_chapter_boundary(
        &mut self,
        boundary: Timestamp,
        target: Timestamp,
    ) -> PositionStatus {
        self.chapter_manager.move_chapter_boundary(boundary, target)
    }

    pub fn seek(&mut self, target: Timestamp) {
        self.tick(target, ControllerState::Seeking);
    }

    pub fn add_chapter(&mut self, ts: Timestamp) {
        if ts >= self.duration {
            // can't add a chapter starting at last position
            return;
        }

        if let Some(new_iter) = self.chapter_manager.add_chapter(ts, self.duration) {
            self.chapter_treeview.get_selection().select_iter(&new_iter);
            self.update_marks();
            self.del_chapter_btn.set_sensitive(true);
        }
    }

    pub fn remove_chapter(&mut self) {
        match self.chapter_manager.remove_selected_chapter() {
            Some(new_iter) => self.chapter_treeview.get_selection().select_iter(&new_iter),
            None => {
                self.chapter_treeview.get_selection().unselect_all();
                self.del_chapter_btn.set_sensitive(false);
            }
        }

        self.update_marks();
    }

    pub fn export_chapters(&self, info: &mut MediaInfo) {
        if let Some((toc, count)) = self.chapter_manager.get_toc() {
            info.toc = Some(toc);
            info.chapter_count = Some(count);
        }
    }
}
<|endoftext|>