<commit_before>--- === cp.apple.finalcutpro.timeline.Timeline ===
---
--- Timeline Module.
--- The timeline module provides an interface to the Final Cut Pro timeline.
--- It delegates to the `contents` property, so any functions which can be called
--- on the `contents` property can be called on the Timeline module.

local require = require

-- local log								= require "hs.logger".new "Timeline"

local axutils							= require "cp.ui.axutils"
local delegator                         = require "cp.delegator"
local Element                           = require "cp.ui.Element"
local go                                = require "cp.rx.go"
local tools                             = require "cp.tools"

local Contents					        = require "cp.apple.finalcutpro.timeline.Contents"
local EffectsBrowser					= require "cp.apple.finalcutpro.main.EffectsBrowser"
local Index                             = require "cp.apple.finalcutpro.timeline.Index"
local PrimaryWindow						= require "cp.apple.finalcutpro.main.PrimaryWindow"
local SecondaryWindow					= require "cp.apple.finalcutpro.main.SecondaryWindow"
local SpeedPopover                      = require "cp.apple.finalcutpro.timeline.SpeedPopover"
local Toolbar					        = require "cp.apple.finalcutpro.timeline.Toolbar"

local cache                             = axutils.cache
local childMatching                     = axutils.childMatching
local childrenWithRole                  = axutils.childrenWithRole
local childWithRole                     = axutils.childWithRole

local Do                                = go.Do
local Done                              = go.Done
local If                                = go.If
local WaitUntil                         = go.WaitUntil

local playErrorSound                    = tools.playErrorSound

local Timeline = Element:subclass("cp.apple.finalcutpro.timeline.Timeline")
    :include(delegator)
    :delegateTo("contents")

--- cp.apple.finalcutpro.timeline.Timeline.matches(element) -> boolean
--- Function
--- Checks to see if an element matches what we think it should be.
---
--- Parameters:
---  * element - An `axuielementObject` to check.
---
--- Returns:
---  * `true` if matches otherwise `false`.
---
--- Notes:
---  * `element` should be an `AXGroup`, which contains an `AXSplitGroup` with an
---    `AXIdentifier` of `_NS:237` (as of Final Cut Pro 10.4)
function Timeline.static.matches(element)
    local splitGroup = childWithRole(element, "AXSplitGroup")
    return element:attributeValue("AXRole") == "AXGroup"
       and splitGroup
       and Timeline.matchesMain(splitGroup)
end

--- cp.apple.finalcutpro.timeline.Timeline.matchesMain(element) -> boolean
--- Function
--- Checks to see if an element matches what we think it should be.
---
--- Parameters:
---  * element - An `axuielementObject` to check.
---
--- Returns:
---  * `true` if matches otherwise `false`
---
--- Notes:
---  * `element` should be an `AXSplitGroup` with an `AXIdentifier` of `_NS:237`
---    (as of Final Cut Pro 10.4)
---  * Because the timeline contents is hard to detect, we look for the timeline
---    toolbar instead.
function Timeline.static.matchesMain(element)
    local parent = element and element:attributeValue("AXParent")
    local group = parent and childWithRole(parent, "AXGroup")
    local buttons = group and childrenWithRole(group, "AXButton")
    return buttons and #buttons >= 6
end

-- _findTimeline(...) -> window | nil
-- Function
-- Gets the Timeline UI.
--
-- Parameters:
--  * ... - Table of elements.
--
-- Returns:
--  * An `axuielementObject` or `nil`
function Timeline.static._findTimeline(...)
    for i = 1,select("#", ...) do
        local window = select(i, ...)
        if window then
            local ui = window:timelineGroupUI()
            if ui then
                local timeline = childMatching(ui, Timeline.matches)
                if timeline then return timeline end
            end
        end
    end
    return nil
end

--- cp.apple.finalcutpro.timeline.Timeline(app) -> Timeline
--- Constructor
--- Creates a new `Timeline` instance.
---
--- Parameters:
---  * app - The `cp.apple.finalcutpro` object.
---
--- Returns:
---  * A new `Timeline` object.
function Timeline:initialize(app)

    local UI = app.UI:mutate(function()
        return cache(self, "_ui", function()
            return Timeline._findTimeline(app.secondaryWindow, app.primaryWindow)
        end,
        Timeline.matches)
    end):monitor(app.primaryWindow.UI, app.secondaryWindow.UI)

    Element.initialize(self, app, UI)
end

--- cp.apple.finalcutpro.timeline.Timeline.isOnSecondary <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is on the Secondary Display.
function Timeline.lazy.prop:isOnSecondary()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and SecondaryWindow.matches(ui:attributeValue("AXWindow"))
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isOnPrimary <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is on the Primary Display.
function Timeline.lazy.prop:isOnPrimary()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and PrimaryWindow.matches(ui:attributeValue("AXWindow"))
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isShowing <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is showing on either the Primary or Secondary display.
function Timeline.lazy.prop:isShowing()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and #ui > 0
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.mainUI <cp.prop: hs.axuielement; read-only>
--- Field
--- Returns the `axuielement` representing the 'timeline', or `nil` if not available.
function Timeline.lazy.prop:mainUI()
    local UI = self.UI
    return UI:mutate(function(original)
        return cache(self, "_main", function()
            local ui = original()
            return ui and childMatching(ui, Timeline.matchesMain)
        end,
        Timeline.matchesMain)
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isPlaying <cp.prop: boolean>
--- Field
--- Is the timeline playing?
function Timeline.lazy.prop:isPlaying()
    return self:app().viewer.isPlaying
end

--- cp.apple.finalcutpro.timeline.Timeline:app() -> App
--- Method
--- Returns the app instance representing Final Cut Pro.
---
--- Parameters:
---  * None
---
--- Returns:
---  * App
function Timeline:app()
    return self:parent()
end

-----------------------------------------------------------------------
--
-- TIMELINE UI:
--
-----------------------------------------------------------------------<commit_msg>Fixes #2925 by checking the `Timeline.contents` for focus instead of the main Timeline UI. (#2931)<commit_after>--- === cp.apple.finalcutpro.timeline.Timeline ===
---
--- Timeline Module.
--- The timeline module provides an interface to the Final Cut Pro timeline.
--- It delegates to the `contents` property, so any functions which can be called
--- on the `contents` property can be called on the Timeline module.

local require = require

-- local log								= require "hs.logger".new "Timeline"

local axutils							= require "cp.ui.axutils"
local delegator                         = require "cp.delegator"
local Element                           = require "cp.ui.Element"
local go                                = require "cp.rx.go"
local tools                             = require "cp.tools"

local Contents					        = require "cp.apple.finalcutpro.timeline.Contents"
local EffectsBrowser					= require "cp.apple.finalcutpro.main.EffectsBrowser"
local Index                             = require "cp.apple.finalcutpro.timeline.Index"
local PrimaryWindow						= require "cp.apple.finalcutpro.main.PrimaryWindow"
local SecondaryWindow					= require "cp.apple.finalcutpro.main.SecondaryWindow"
local SpeedPopover                      = require "cp.apple.finalcutpro.timeline.SpeedPopover"
local Toolbar					        = require "cp.apple.finalcutpro.timeline.Toolbar"

local cache                             = axutils.cache
local childMatching                     = axutils.childMatching
local childrenWithRole                  = axutils.childrenWithRole
local childWithRole                     = axutils.childWithRole

local Do                                = go.Do
local Done                              = go.Done
local If                                = go.If
local WaitUntil                         = go.WaitUntil

local playErrorSound                    = tools.playErrorSound

local Timeline = Element:subclass("cp.apple.finalcutpro.timeline.Timeline")
    :include(delegator)
    :delegateTo("contents")

--- cp.apple.finalcutpro.timeline.Timeline.matches(element) -> boolean
--- Function
--- Checks to see if an element matches what we think it should be.
---
--- Parameters:
---  * element - An `axuielementObject` to check.
---
--- Returns:
---  * `true` if matches otherwise `false`.
---
--- Notes:
---  * `element` should be an `AXGroup`, which contains an `AXSplitGroup` with an
---    `AXIdentifier` of `_NS:237` (as of Final Cut Pro 10.4)
function Timeline.static.matches(element)
    local splitGroup = childWithRole(element, "AXSplitGroup")
    return element:attributeValue("AXRole") == "AXGroup"
       and splitGroup
       and Timeline.matchesMain(splitGroup)
end

--- cp.apple.finalcutpro.timeline.Timeline.matchesMain(element) -> boolean
--- Function
--- Checks to see if an element matches what we think it should be.
---
--- Parameters:
---  * element - An `axuielementObject` to check.
---
--- Returns:
---  * `true` if matches otherwise `false`
---
--- Notes:
---  * `element` should be an `AXSplitGroup` with an `AXIdentifier` of `_NS:237`
---    (as of Final Cut Pro 10.4)
---  * Because the timeline contents is hard to detect, we look for the timeline
---    toolbar instead.
function Timeline.static.matchesMain(element)
    local parent = element and element:attributeValue("AXParent")
    local group = parent and childWithRole(parent, "AXGroup")
    local buttons = group and childrenWithRole(group, "AXButton")
    return buttons and #buttons >= 6
end

-- _findTimeline(...) -> window | nil
-- Function
-- Gets the Timeline UI.
--
-- Parameters:
--  * ... - Table of elements.
--
-- Returns:
--  * An `axuielementObject` or `nil`
function Timeline.static._findTimeline(...)
    for i = 1,select("#", ...) do
        local window = select(i, ...)
        if window then
            local ui = window:timelineGroupUI()
            if ui then
                local timeline = childMatching(ui, Timeline.matches)
                if timeline then return timeline end
            end
        end
    end
    return nil
end

--- cp.apple.finalcutpro.timeline.Timeline(app) -> Timeline
--- Constructor
--- Creates a new `Timeline` instance.
---
--- Parameters:
---  * app - The `cp.apple.finalcutpro` object.
---
--- Returns:
---  * A new `Timeline` object.
function Timeline:initialize(app)

    local UI = app.UI:mutate(function()
        return cache(self, "_ui", function()
            return Timeline._findTimeline(app.secondaryWindow, app.primaryWindow)
        end,
        Timeline.matches)
    end):monitor(app.primaryWindow.UI, app.secondaryWindow.UI)

    Element.initialize(self, app, UI)
end

--- cp.apple.finalcutpro.timeline.Timeline.isOnSecondary <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is on the Secondary Display.
function Timeline.lazy.prop:isOnSecondary()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and SecondaryWindow.matches(ui:attributeValue("AXWindow"))
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isOnPrimary <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is on the Primary Display.
function Timeline.lazy.prop:isOnPrimary()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and PrimaryWindow.matches(ui:attributeValue("AXWindow"))
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isShowing <cp.prop: boolean; read-only>
--- Field
--- Checks if the Timeline is showing on either the Primary or Secondary display.
function Timeline.lazy.prop:isShowing()
    return self.UI:mutate(function(original)
        local ui = original()
        return ui ~= nil and #ui > 0
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.mainUI <cp.prop: hs.axuielement; read-only>
--- Field
--- Returns the `axuielement` representing the 'timeline', or `nil` if not available.
function Timeline.lazy.prop:mainUI()
    local UI = self.UI
    return UI:mutate(function(original)
        return cache(self, "_main", function()
            local ui = original()
            return ui and childMatching(ui, Timeline.matchesMain)
        end,
        Timeline.matchesMain)
    end)
end

--- cp.apple.finalcutpro.timeline.Timeline.isPlaying <cp.prop: boolean>
--- Field
--- Is the timeline playing?
function Timeline.lazy.prop:isPlaying()
    return self:app().viewer.isPlaying
end

--- cp.apple.finalcutpro.timeline.Timeline.isFocused <cp.prop: boolean>
--- Field
--- Is the timeline focused?
function Timeline.lazy.prop:isFocused()
    return self.contents.isFocused
end

--- cp.apple.finalcutpro.timeline.Timeline:app() -> App
--- Method
--- Returns the app instance representing Final Cut Pro.
---
--- Parameters:
---  * None
---
--- Returns:
---  * App
function Timeline:app()
    return self:parent()
end

-----------------------------------------------------------------------
--
-- TIMELINE UI:
--
-----------------------------------------------------------------------
{'fraction_non_alphanumeric': 0.14008463446665695, 'fraction_numerical': 0.0027725083904859187, 'mean_word_length': 2.2973298051479434, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6717527', 'n_tokens_mistral': 3983, 'n_tokens_neox': 3831, 'n_words': 1083}