Posted to tcl by Napier at Wed Apr 15 19:58:24 GMT 2015view pretty
### 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"} }