|| Copyright (C) 1998-2008, Sumisho Computer Systems Corp.  All Rights Reserved.

|| ------------------------------------------------------------------------ 

{doc-next
    {purpose A basic implementation of the {docref StyleManager}
        class.
    }
    {since 7.0}
}
{define-class public open BasicStyleManager {inherits StyleManager}

  {doc-next
    {purpose An index used to find rules that may apply to a
        given {docref Visual.style-element}.
    }
    {details The key is an element pattern. The value is an array of
        {docref AtomicStyleRule}s sorted with the highest priority
        rules first.
    }
    {since 7.0}
  }
  field protected index:{HashTable-of String, StyleRuleArray} = 
      {new {HashTable-of String, StyleRuleArray}}

  field private file-prio:int = priority-multiplier

  {doc-next
    {purpose The number of base style rules when the index was last built.}
    {details
        If new base style rules are added, the index will be rebuilt.
    }
  }
  field protected base-rule-count:int

  {doc-next
    {purpose The {docref LookAndFeel} used to initialize this object.}
  }
  field protected look-and-feel:#LookAndFeel

  {doc-next
    {purpose Initialize a new {docref BasicStyleManager}.}
    {parameter style, The StyleSheet used to configure this style manager.}
    {parameter look-and-feel, If a {docref StyledLookAndFeel} is in
        use, a {ctext StyleManager} must include its default style
        rules.  If this parameter is not supplied, the rules from
        {docref the-default-look-and-feel} are used.
    }
    {since 7.0}
  }
  {constructor public {default 
                          style-sheet:StyleSheet,
                          look-and-feel:#LookAndFeel = 
                              the-default-look-and-feel.target-look-and-feel
                      }
    {construct-super style-sheet}
    set self.look-and-feel = look-and-feel
    set self.base-rule-count = StyleManager.base-style-rules.size
    {self.rebuild-index}
  }

  {doc-next
    {purpose Discard the style rule index and rebuild it.}
  }
  {method protected open {rebuild-index}:void
    set self.file-prio = priority-multiplier

    || First the "base rules"
    {self.add-rules StyleManager.base-style-rules}
    {inc self.file-prio, priority-multiplier}

    || Grab the rules from a StyledLookAndFeel
    {type-switch self.look-and-feel
     case slnf:StyledLookAndFeel do
        {self.import slnf.combined-style-sheet}
    }

    || Now the style sheet that was passed in
    {self.import self.style-sheet}

    || Cook the rules for quick access later
    || Sort each group of atomic rules to put higher priority rules first
    {for ra:StyleRuleArray in self.index do
        {ra.sort
            comparison-proc = 
                {proc {a:AtomicStyleRule, b:AtomicStyleRule}:bool
                    {return a.priority > b.priority}
                }
        }
    }
  }

  {doc-next
    {purpose Rebuild the style rule index if new rules have been
        added by calling {docref StyleManager.append-base-style-rule}.
    }
  }
  {method protected open {refresh-index}:void
    {if  self.base-rule-count != StyleManager.base-style-rules.size then
        set self.base-rule-count = StyleManager.base-style-rules.size
        {self.rebuild-index}
    }
  }


  {doc-next
    {purpose Instantiate a {docref StyleSheet} from the file at
        {param location} and use it to initialize a {docref
        BasicStyleManager}.
    }
    {since 7.0}
  }
  {factory public implicit {from-url location:Url}:BasicStyleManager
    {return {BasicStyleManager {StyleSheet location}}}
  }

  {doc-next
    {purpose Create a {docref StyleManager} from a {docref StyleSheet}.}
  }
  {factory public implicit {from-StyleSheet-factory 
                               style-sheet:StyleSheet
                           }:BasicStyleManager
    {return {BasicStyleManager style-sheet}}
  }


  {method private {import ss:StyleSheet}:void
    {for iss:StyleSheet in ss.imports do
        {self.import iss}
        {inc self.file-prio, priority-multiplier}
    }
    {self.add-rules ss.rules}
    {inc self.file-prio, priority-multiplier}
  }

  {method private {add-rules rules:{Array-of StyleRule}}:void
    {for sr:StyleRule in rules do
        {self.add-rule sr.selectors, sr.bindings}
    }
  }

  {doc-next
    {purpose Add a rule to this {docref StyleManager}.}
    {parameter selectors, The selector strings for this rule. If any
        of these strings match, the rule will be applied. 
    }
    {parameter bindings, A mapping from option names to option
        values. If a rule matches an object, the options are bound
        on that object.
    }
    {details This method takes a rule as specified in a style sheet
        and stores it for quick access later. The rule is converted
        into a number of primitive rules, where a primitive rule
        has one selector and one option binding.
    }
    {since 7.0}
  }
  {method protected open {add-rule 
                          selectors:StringArray,
                          bindings:{HashTable-of String, any}
                      }:void
    {for pattern:String in selectors do
        let p:StyleSelectorPattern = {StyleSelectorPattern pattern}
        {for val:any key name in bindings do
            let sr:AtomicStyleRule = 
                {AtomicStyleRule p, name, val, self.file-prio + p.priority}
            {self.index-rule sr}
        }
    }
  }

  {method private {index-rule rule:AtomicStyleRule}:void
    let rname:String = rule.pattern.key
    let (ra:StyleRuleArray, found?:bool) = {self.index.get-if-exists rname}
    {if found? then
        {ra.append rule}
     else
        set self.index[rname] = {StyleRuleArray rule}
    }
  }


  {method public open {apply-style v:Visual}:void

    {self.refresh-index}
    let v-class:String = v.style-class
    let v-element:String = v.style-element
    {if v-class == "" and v-element == "" then 
        {return}
    }

    let assigned-props:{Set-of String} = {new {Set-of String}}
    let ra:StyleRuleArray = {uninitialized-value-for-type StyleRuleArray}
    let found?:bool = false

    || First look for rules that match a specific element
    {if v-element != "" then
        set (ra, found?) = {self.index.get-if-exists v-element}
        {if found? then
            {for r:AtomicStyleRule in ra do
                {if {assigned-props.member? r.name} then
                    {continue}
                }
                {if {r.pattern.match? v} then
                    {if-non-null opt-key:OptionKey = 
                        {v.name-to-option-key r.name}
                     then
                        let rval:any = 
                            {if r.value == unbound-option then 
                                || This is undocumented
                                {prefs}[r.name]
                             else 
                                {type-switch r.value
                                 case p:{proc-type {}:any} do
                                    || This is undocumented, needed for View
                                    {p}
                                 case p:{proc-type {target:Visual}:any} do
                                    || This is undocumented
                                    {p v}
                                 else
                                    r.value
                                }
                            } asa opt-key.type
                        {v.add-style-option opt-key, rval}
                        {assigned-props.insert r.name}
                     else
                        {error 
                            {hlformat "Unknown option %1!s! for %2!s!", 
                                r.name, r.pattern
                            }
                        }
                    }
                }
            }
        }
    }

    set (ra, found?) = {self.index.get-if-exists ""}
    {if found? then
        {for r:AtomicStyleRule in ra do
            {if {assigned-props.member? r.name} then
                {continue}
            }
            {if {r.pattern.match? v} then
                {if-non-null opt-key:OptionKey = {v.name-to-option-key r.name} 
                 then
                    let rval:any = 
                        {if r.value == unbound-option then 
                            || This is undocumented
                            {prefs}[r.name]
                         else 
                            {type-switch r.value
                             case p:{proc-type {}:any} do
                                || This is undocumented, needed for View
                                {p}
                             case p:{proc-type {target:Visual}:any} do
                                || This is undocumented
                                {p v}
                             else
                                r.value
                            }
                        } asa opt-key.type
                    {v.add-style-option opt-key, rval}
                    {assigned-props.insert r.name}
                 else
                    {error 
                        {hlformat "Unknown option %1!s! for %2!s!", 
                            r.name, r.pattern
                        }
                    }
                }
            }
        }
    }
  }  
}

{do
    {StyleManager.set-default {BasicStyleManager {StyleSheet}}}
}
