Posted to tcl by Napier at Wed Apr 15 19:58:24 GMT 2015view raw
- ### Tree Database for URC Interfaces
- ### Author: Braden R. Napier
- ## Description:
- # This Script will provide a simple means to handling navigation of
- # elements in a tree-like fashion. You can nest containers within
- # containers and it will provide all the tools to browse and parse
- # the elements of each container. You can also easily add elements
- # such as new categories, values in the dictionaries, etc.
- # List Object: browse_list
- # Callback : When an Item is Selected See the ::Browse::Item::Open Procedure
- # so you can adjust the action that will take place.
- # ::Widgets::Meta::BrowseTitle : You may want to change this as it is meant
- # to list the title of the page to the container
- # being viewed. Define whatever you like.
- # Set Testing to 1 When Using in TCL Shell or Outside of the Original Script
- # it was Designed For. This will make it so the script won't error out.
- set testing 1
- namespace eval Browse {
- variable History ""
- variable CurrentList [dict create]
- variable CurrentContainer ""
- variable CurrentCategory ""
- variable Stations [dict create]
- variable Refresh 0
- ## Provide the Initial Framework for the Containers
- # This will be added to using the procedures below
- # as new content becomes available.
- variable Containers [dict create \
- ccount 1 \
- icount 0 \
- name "Root" \
- type "root" \
- c [dict create \
- 0 [dict create \
- name "Music Services" \
- type "category" \
- ccount 0 \
- icount 0 \
- ] \
- ] \
- ]
- variable Sort [dict create \
- ABC "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" \
- ]
- # Item Formatting
- # i {0 {name "Item Name" type "Item Type" id "Item ID"}}
- proc Menu {i} {
- ## Opens the Initial Container Desired
- # 0 = iTunes Radio
- # 1 = Purchased Music
- # 2 = Purchased Movies
- # 3 = Purchased TV Shows
- LOG "User Begins Browsing: $i"
- set ::Browse::CurrentCategory $i
- ::Browse::Container::Open $i
- }
- proc Start {} {
- ## Called to Startup the Browse System
- LOG "::Browse::Start"
- ::Browse::List::Reset
- ::Browse::Sources::Update
- }
- proc AddToPath {contents args} {
- # Used to place $contents at the path specified by $args
- # will replace any data with the same keys but will not affect keys that
- # are not included in $contents (it will unpack contents and set them one-by-one)
- # ccount and icount will be ignored if provided as a safety measure, the script
- # will count the items provided and validate before adding to the container
- LOG "::Containers::AddToPath $args"
- set path [::Browse::Container::Path $args]
- if {[dict exists $::Browse::Containers {*}$path]} {
- if {[catch {dict keys $contents}]} {
- LOG "ERROR: $contents is not a Valid Dictionary"
- return
- }
- dict for {k v} $contents {
- switch -- $k {
- "c" {
- if {[catch {dict keys $v}]} {
- LOG "ERROR: Provided Container Value is Not a Valid Dictionary"
- continue
- }
- set ccount [llength [dict keys $v]]
- dict set ::Browse::Containers {*}$path c $v
- dict set ::Browse::Containers {*}$path ccount $ccount
- }
- "i" {
- if {[catch {dict keys $v}]} {
- LOG "ERROR: Provided Items Value is Not a Valid Dictionary"
- continue
- }
- set icount [llength [dict keys $v]]
- dict set ::Browse::Containers {*}$path i $v
- dict set ::Browse::Containers {*}$path icount $icount
- }
- "ccount" {LOG "IGNORING PROVIDED CCOUNT!"; continue}
- "icount" {LOG "IGNORING PROVIDED ICOUNT!"; continue}
- default {
- # If Not a c or i value, we will add it to the path for manual use
- # Check if Replacing Current Data
- if {[dict exists $::Browse::Containers {*}$path $k]} {
- LOG "REPLACING CURRENT DATA at $path $k!"
- }
- dict set ::Browse::Containers {*}$path $k $v
- }
- }
- }
- } else {
- LOG "ERROR: Provided Path $args Does Not Exist!"
- }
- }
- namespace eval Container {
- proc Back {args} {
- ## Navigate Back One Container
- LOG "::Browse::Container::Back $args"
- if {$args == ""} {
- if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer]} {
- LOG "----- DELETING A CONTAINER $::Browse::CurrentContainer!!!! -----"
- if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer i]} {
- dict set ::Browse::Containers {*}$::Browse::CurrentContainer icount 0
- dict unset ::Browse::Containers {*}$::Browse::CurrentContainer i
- }
- if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer c]} {
- dict set ::Browse::Containers {*}$::Browse::CurrentContainer ccount 0
- dict unset ::Browse::Containers {*}$::Browse::CurrentContainer c
- }
- }
- set ::Browse::Refresh 1
- ::Browse::Container::Open {*}[::Browse::Container::GetParent]
- } else {
- ## TODO: Use Args to Define Multiple Jump Backs
- }
- }
- proc Refresh {} {
- ## Refreshes the Currently Viewed Container List
- # - Generally this Means New Data Arrived for the Viewed Container
- LOG "::Browse::Container::Refresh"
- set ::Browse::Refresh 1
- ::Browse::Container::Open {*}$::Browse::CurrentContainer
- }
- proc Open {args} {
- ## ::Browse::Container::Open <path>
- # Opens and Displays the Given Container by first parsing all
- # container items (folders) then
- LOG "::Browse::Container::Open $args"
- set path [::Browse::Container::Path $args]
- set ::Browse::CurrentContainer $path
- LOG "Open Container: $path"
- ::Browse::List::Reset
- if {[dict exists $::Browse::Containers {*}$path]} {
- set d [dict get $::Browse::Containers {*}$path]
- set type [dict get $d type]
- set containerName [dict get $d name]
- LOG "Container Name: $containerName"
- if {$::Browse::Refresh == 0} {
- if {[dict exists $d c]} {set ds $d; dict unset ds c} else {set ds $d}
- ::Widgets::Browse::Container {*}$ds
- } else {set ::Browse::Refresh 0}
- switch -glob -nocase -- $type {
- co* {
- # Container is a Container
- set ilist [::Browse::List::GetIcon ocontainer]
- set clist $containerName
- ::Browse::List::Add [list $clist] $ilist
- set ifCategory 0
- }
- ca* {
- # Container is a Category Container
- set ifCategory 1
- }
- default {set ifCategory 0}
- }
- if {[dict exists $d ccount] && [dict get $d ccount] > 0} {
- set c [dict get $d c]
- set clist ""; # Container Name List
- set ilist ""; # Icon List
- dict for {k v} $c {
- set type [dict get $v type]
- if {!$ifCategory} {
- lappend clist " [dict get $v name]"
- lappend ilist " [::Browse::List::GetIcon $type]"
- } else {
- lappend clist [dict get $v name]
- lappend ilist [::Browse::List::GetIcon $type]
- }
- }
- ::Browse::List::Add $clist $ilist
- }
- if {[dict exists $d icount] && [dict get $d icount] > 0} {
- set i [dict get $d i]
- set clist ""; # Item List
- set ilist ""; # Icon List
- dict for {k v} $i {
- set type [dict get $v type]
- if {!$ifCategory} {
- lappend clist " [dict get $v name]"
- lappend ilist " [::Browse::List::GetIcon $type]"
- } else {
- lappend clist [dict get $v name]
- lappend ilist [::Browse::List::GetIcon $type]
- }
- }
- ::Browse::List::Add $clist $ilist
- }
- }
- }
- proc Add {name items params args} {
- ## ::Browse::Container::Add
- # Adds a Container to the Container List for Browsing and Selection
- # ::Browse::Container::Add "Apple Stations" $itemDict 0
- ## Parent Container Must Exist
- # Automatically increments the count values
- # Params will add params for the container such as ID's or anything else
- # into the params key
- LOG "::Browse::Container::Add -name $name -items $items -path $args"
- set args [string map {"c " ""} $args]
- set path c
- foreach p $args {
- lappend path $p
- if {![dict exists $::Browse::Containers {*}$path]} {
- LOG "Parent Container Doesn't Exist, Create it First!"
- LOG "Current Path: $path"
- return
- }
- lappend path c
- }
- LOG "Final Container Path: $path"
- set cPath [lrange $path 0 end-1]
- set check -1
- if {[catch {dict keys $items}]} {
- puts "ERROR: Items Not Dictionary"
- return
- }
- if {[dict exists $::Browse::Containers {*}$cPath ccount]} {
- set ccount [dict get $::Browse::Containers {*}$cPath ccount]
- if {$ccount > 0} {
- ## If Container Already Exists, Replace
- set d [dict get $::Browse::Containers {*}$cPath c]
- dict for {k v} $d {
- if {[dict exists $v name] && [dict get $v name] == $name} {
- set check $k
- break
- }
- }
- }
- if {$check == -1} {
- incr ccount
- dict set ::Browse::Containers {*}$cPath ccount $ccount
- set index [expr {$ccount - 1}]
- } else {
- set index $check
- }
- set icount [llength [dict keys $items]]
- dict set ::Browse::Containers {*}$path $index name $name
- dict set ::Browse::Containers {*}$path $index params [dict create {*}$params index $index]
- dict set ::Browse::Containers {*}$path $index type container
- if {![dict exists $::Browse::Containers {*}$path $index ccount]} {
- dict set ::Browse::Containers {*}$path $index ccount 0
- }
- dict set ::Browse::Containers {*}$path $index icount $icount
- if {$icount > 0} {
- dict set ::Browse::Containers {*}$path $index i $items
- }
- # Return the Path to the Procedure that Called it
- LOG "New Containers Value:\n$::Browse::Containers"
- return "$path $index"
- }
- }
- proc Multiple {names args} {
- ## Creates Multiple Containers at path identified by $args
- # Containers Will be Empty, All Containers at path will be replaced
- # Example Call:
- # ::Browse::Container::Multiple [list Songs Artists Albums Genres Composers] 1
- LOG "::Browse::Container::Multiple $names $args"
- set path [::Browse::Container::Path $args]
- if {[dict exists $::Browse::Containers {*}$path]} {
- set ccount [llength $names]
- dict set ::Browse::Containers {*}$path ccount $ccount
- set i 0
- set c [dict create]
- foreach name $names {
- # 0 {name {Apple Stations} type container ccount 0 icount 0}
- dict set c $i [dict create name $name type container ccount 0 icount 0]
- incr i
- }
- dict set ::Browse::Containers {*}$path c $c
- LOG "New Container: [dict get $::Browse::Containers {*}$path]"
- } else {
- LOG "ERROR: Given Path $path Does Not Exist"
- }
- }
- proc lappend {containers args} {
- ## Adds the provided $containers to the path identified by $args
- # Index provided for items will be ignored and replaced by proper
- # values. No check will be done so be careful not to provide
- # duplicate data
- set path [::Browse::Container::Path $args]
- if {[dict exists $::Browse::Containers {*}$path]} {
- set ccount [dict get $::Browse::Containers {*}$path ccount]
- dict for {k v} $items {
- dict set ::Browse::Containers {*}$path c $ccount $v
- incr ccount
- }
- dict set ::Browse::Containers {*}$path ccount $ccount
- LOG "New Container: [dict get $::Browse::Containers {*}$path]"
- } else {
- LOG "ERROR: Given Path $path Does Not Exist"
- }
- }
- proc Replace {containers args} {
- # Replaces Containers at Location Specified by args - should be
- # properly formatted containers and items inside of them (if applicable)
- # Will automatically adjust ccount and any items in the container
- # will be left alone
- set args [string map {"c " ""} $args]
- set path c
- foreach p $args {
- lappend path $p
- if {![dict exists $::Browse::Containers {*}$path]} {
- LOG "Parent Container Doesn't Exist, Create it First!"
- LOG "Current Path: $path"
- return
- }
- lappend path c
- }
- LOG "Final Container Path: $path"
- set cPath [lrange $path 0 end-1]
- set check -1
- if {[catch {dict keys $containers}]} {
- puts "ERROR: Containers Not Dictionary"
- return
- }
- if {[dict exists $::Browse::Containers {*}$cPath ccount]} {
- set ccount [dict get $::Browse::Containers {*}$cPath ccount]
- if {$ccount > 0} {LOG "Replacing Previous Containers"}
- }
- set ccount [llength [dict keys $containers]]
- dict set ::Browse::Containers {*}$cPath ccount $ccount
- dict set ::Browse::Containers {*}$path $containers
- LOG "Containers at $path Replaced!"
- LOG "[dict get $::Browse::Containers {*}$cPath]"
- }
- proc GetIndex {name args} {
- # Get the Index of the Container at the Given Path by Name
- # ::Browse::Container::GetIndex "Apple Stations" 0
- # -> 0 c 0
- }
- proc GetParent {args} {
- ## Get the Parent Container of the Container at args
- # If Left Blank, Returns Parent of Current Container
- if {$args == ""} {
- set path $::Browse::CurrentContainer
- } else {
- set path $args
- }
- set parent [lrange $path 0 end-2]
- puts "Parent Container: $parent"
- set name [dict get $::Browse::Containers {*}$parent name]
- puts "Parent Name: $name"
- if {$name == "Root"} {
- LOG "Already at Root Level Returning $path"
- set rootPath [string map {"c " ""} $args]
- return $rootPath
- } else {
- set parentPath [string map {"c " ""} $parent]
- return $parentPath
- }
- }
- proc Path {args} {
- set args [string map {"c " ""} $args]
- set path c
- foreach p {*}$args {
- lappend path $p
- if {![dict exists $::Browse::Containers {*}$path]} {
- puts "Parent Container Doesn't Exist, Create it First!"
- puts "Current Path: $path"
- return
- }
- lappend path c
- }
- if {[lindex $path end] eq "c"} {set path [lrange $path 0 end-1]}
- return $path
- }
- }
- namespace eval Item {
- proc Send {args} {
- ## Sends the Item to the System Script to Be Executed
- LOG "::Browse::Item::Send $args"
- ::device_proc "::ATV::Control::PlayItem $args"
- }
- proc Open {args} {
- ## Open an Item at the Given Path
- # ::Browse::Item::Open 0 0 2 <- Item 3 in Container 0 -> 0
- LOG "::Browse::Item::Open $args"
- set args [string map {"c " ""} $args]
- set itemIndex [lindex $args end]
- set containerPath [lrange $args 0 end-1]
- set cPath [::Browse::Container::Path $containerPath]
- if {[dict exists $::Browse::Containers {*}$cPath i $itemIndex]} {
- ## Item Exists at Given Path!
- set d [dict get $::Browse::Containers {*}$cPath i $itemIndex]
- ::Browse::Item::Send $d
- }
- }
- proc Add {type name id params args} {
- ## Adds a single Item to the Given Container found at $args
- LOG "::Browse::Item::Add $type $name $id $args"
- set path [::Browse::Container::Path $args]
- if {[dict exists $::Browse::Containers {*}$path]} {
- set icount [dict get $::Browse::Containers {*}$path icount]
- incr icount
- dict set ::Browse::Containers {*}$path icount $icount
- set index [expr {$icount - 1}]
- dict set ::Browse::Containers {*}$path i $index \
- [dict create type $type name $name id $id params $params]
- LOG "New Container: [dict get $::Browse::Containers {*}$path]"
- } else {
- LOG "ERROR: Given Path $path Does Not Exist"
- }
- }
- proc Multiple {items args} {
- ## Adds Multiple Items to the Given Container found at $args
- # Items Must Be Formatted as a List of Items Properly Formatted. Their Index
- # will be converted automatically.
- set path [::Browse::Container::Path $args]
- if {[dict exists $::Browse::Containers {*}$path]} {
- set icount [dict get $::Browse::Containers {*}$path icount]
- dict for {k v} $items {
- dict set ::Browse::Containers {*}$path i $icount $v
- incr icount
- }
- dict set ::Browse::Containers {*}$path icount $icount
- LOG "New Container: [dict get $::Browse::Containers {*}$path]"
- } else {
- LOG "ERROR: Given Path $path Does Not Exist"
- }
- }
- proc Replace {items args} {
- ## Replaces Items at $args with $items
- # The item dict should be properly formatted
- # 0 {type "Item Type" name "Item Name" id "ID"} 1 ...
- LOG "::Browse::Item::Multiple $items $args"
- set path [::Browse::Container::Path $args]
- if {[catch {dict keys $items}]} {LOG "$items is not a Dictionary"; return}
- if {[dict exists $::Browse::Containers {*}$path]} {
- set icount [llength [dict keys $items]]
- dict set ::Browse::Containers {*}$path icount $icount
- dict set ::Browse::Containers {*}$path i $items
- LOG "New Container: [dict get $::Browse::Containers {*}$path]"
- } else {
- LOG "ERROR: Given Path $path Does Not Exist"
- }
- }
- }
- namespace eval List {
- proc Reset {} {
- ::clear_list "browse_list"
- }
- proc Select {index} {
- # User Selects an Item in the List
- LOG "::Browse::List::Select $index"
- set c [dict get $::Browse::Containers {*}$::Browse::CurrentContainer]
- dict with c {}
- switch -glob -- $type {
- co* {
- # Browsing Container
- if {$index == 0} {
- # Navigate Back One
- ::Browse::Container::Back
- return
- } else {
- set index [expr {$index - 1}]
- }
- }
- ca* {
- # Browsing Category
- }
- default {
- LOG "ERROR: Browsing Unspecified Category - $type!"
- return
- }
- }
- set ifItem 0
- if {$ccount > 0} {
- set cindex [expr {$ccount - 1}]
- if {$index > $cindex} {
- set ifItem 1
- } else {
- LOG "Container Number: $index"
- ::Browse::Container::Open {*}$::Browse::CurrentContainer $index
- }
- } else {set ifItem 1}
- if {$ifItem && $icount > 0} {
- set index [expr {$index - $ccount}]
- LOG "Item Number: $index"
- ::Browse::Item::Open {*}$::Browse::CurrentContainer $index
- }
- }
- proc Add {l i} {
- ## List Containers
- ::add_to_list "browse_list" $l $i
- }
- proc GetIcon {t} {
- # Set the Icon to Return for Given Item or Container Types
- switch -nocase -glob -- $t {
- So* -
- St* {return s; # Station }
- Co* {return $; # Container }
- Oc* {return %; # Ocontainer (Open Container)}
- }
- }
- }
- }
- if {$testing == 1} {
- proc LOG {t} {puts $t}
- namespace eval Widgets {
- namespace eval Meta {
- proc BrowseTitle {args} {puts "Simulating Browse Title to $args"}
- }
- namespace eval Browse {
- proc Container {args} {puts "User is Browsing Container $args"}
- }
- }
- proc add_to_list {args} {puts "Simulating Add to List $args"}
- proc clear_list {args} {puts "Simulating Clear List $args"}
- }