Posted to tcl by Napier at Wed Apr 15 19:58:24 GMT 2015view raw

  1. ### Tree Database for URC Interfaces
  2. ### Author: Braden R. Napier
  3. ## Description:
  4. # This Script will provide a simple means to handling navigation of
  5. # elements in a tree-like fashion. You can nest containers within
  6. # containers and it will provide all the tools to browse and parse
  7. # the elements of each container. You can also easily add elements
  8. # such as new categories, values in the dictionaries, etc.
  9.  
  10. # List Object: browse_list
  11. # Callback : When an Item is Selected See the ::Browse::Item::Open Procedure
  12. # so you can adjust the action that will take place.
  13. # ::Widgets::Meta::BrowseTitle : You may want to change this as it is meant
  14. # to list the title of the page to the container
  15. # being viewed. Define whatever you like.
  16.  
  17. # Set Testing to 1 When Using in TCL Shell or Outside of the Original Script
  18. # it was Designed For. This will make it so the script won't error out.
  19. set testing 1
  20.  
  21. namespace eval Browse {
  22.  
  23. variable History ""
  24. variable CurrentList [dict create]
  25. variable CurrentContainer ""
  26. variable CurrentCategory ""
  27. variable Stations [dict create]
  28. variable Refresh 0
  29.  
  30. ## Provide the Initial Framework for the Containers
  31. # This will be added to using the procedures below
  32. # as new content becomes available.
  33.  
  34. variable Containers [dict create \
  35. ccount 1 \
  36. icount 0 \
  37. name "Root" \
  38. type "root" \
  39. c [dict create \
  40. 0 [dict create \
  41. name "Music Services" \
  42. type "category" \
  43. ccount 0 \
  44. icount 0 \
  45. ] \
  46. ] \
  47. ]
  48.  
  49. variable Sort [dict create \
  50. 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" \
  51. ]
  52.  
  53. # Item Formatting
  54. # i {0 {name "Item Name" type "Item Type" id "Item ID"}}
  55.  
  56. proc Menu {i} {
  57. ## Opens the Initial Container Desired
  58. # 0 = iTunes Radio
  59. # 1 = Purchased Music
  60. # 2 = Purchased Movies
  61. # 3 = Purchased TV Shows
  62. LOG "User Begins Browsing: $i"
  63. set ::Browse::CurrentCategory $i
  64. ::Browse::Container::Open $i
  65. }
  66.  
  67. proc Start {} {
  68. ## Called to Startup the Browse System
  69. LOG "::Browse::Start"
  70. ::Browse::List::Reset
  71. ::Browse::Sources::Update
  72.  
  73. }
  74.  
  75. proc AddToPath {contents args} {
  76. # Used to place $contents at the path specified by $args
  77. # will replace any data with the same keys but will not affect keys that
  78. # are not included in $contents (it will unpack contents and set them one-by-one)
  79. # ccount and icount will be ignored if provided as a safety measure, the script
  80. # will count the items provided and validate before adding to the container
  81. LOG "::Containers::AddToPath $args"
  82. set path [::Browse::Container::Path $args]
  83. if {[dict exists $::Browse::Containers {*}$path]} {
  84. if {[catch {dict keys $contents}]} {
  85. LOG "ERROR: $contents is not a Valid Dictionary"
  86. return
  87. }
  88. dict for {k v} $contents {
  89. switch -- $k {
  90. "c" {
  91. if {[catch {dict keys $v}]} {
  92. LOG "ERROR: Provided Container Value is Not a Valid Dictionary"
  93. continue
  94. }
  95. set ccount [llength [dict keys $v]]
  96. dict set ::Browse::Containers {*}$path c $v
  97. dict set ::Browse::Containers {*}$path ccount $ccount
  98. }
  99. "i" {
  100. if {[catch {dict keys $v}]} {
  101. LOG "ERROR: Provided Items Value is Not a Valid Dictionary"
  102. continue
  103. }
  104. set icount [llength [dict keys $v]]
  105. dict set ::Browse::Containers {*}$path i $v
  106. dict set ::Browse::Containers {*}$path icount $icount
  107. }
  108. "ccount" {LOG "IGNORING PROVIDED CCOUNT!"; continue}
  109. "icount" {LOG "IGNORING PROVIDED ICOUNT!"; continue}
  110. default {
  111. # If Not a c or i value, we will add it to the path for manual use
  112.  
  113. # Check if Replacing Current Data
  114. if {[dict exists $::Browse::Containers {*}$path $k]} {
  115. LOG "REPLACING CURRENT DATA at $path $k!"
  116. }
  117. dict set ::Browse::Containers {*}$path $k $v
  118. }
  119. }
  120. }
  121. } else {
  122. LOG "ERROR: Provided Path $args Does Not Exist!"
  123. }
  124. }
  125.  
  126. namespace eval Container {
  127.  
  128. proc Back {args} {
  129. ## Navigate Back One Container
  130. LOG "::Browse::Container::Back $args"
  131. if {$args == ""} {
  132. if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer]} {
  133. LOG "----- DELETING A CONTAINER $::Browse::CurrentContainer!!!! -----"
  134. if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer i]} {
  135. dict set ::Browse::Containers {*}$::Browse::CurrentContainer icount 0
  136. dict unset ::Browse::Containers {*}$::Browse::CurrentContainer i
  137. }
  138. if {[dict exists $::Browse::Containers {*}$::Browse::CurrentContainer c]} {
  139. dict set ::Browse::Containers {*}$::Browse::CurrentContainer ccount 0
  140. dict unset ::Browse::Containers {*}$::Browse::CurrentContainer c
  141. }
  142. }
  143. set ::Browse::Refresh 1
  144. ::Browse::Container::Open {*}[::Browse::Container::GetParent]
  145. } else {
  146. ## TODO: Use Args to Define Multiple Jump Backs
  147. }
  148. }
  149.  
  150. proc Refresh {} {
  151. ## Refreshes the Currently Viewed Container List
  152. # - Generally this Means New Data Arrived for the Viewed Container
  153. LOG "::Browse::Container::Refresh"
  154. set ::Browse::Refresh 1
  155. ::Browse::Container::Open {*}$::Browse::CurrentContainer
  156. }
  157.  
  158. proc Open {args} {
  159. ## ::Browse::Container::Open <path>
  160. # Opens and Displays the Given Container by first parsing all
  161. # container items (folders) then
  162. LOG "::Browse::Container::Open $args"
  163. set path [::Browse::Container::Path $args]
  164. set ::Browse::CurrentContainer $path
  165. LOG "Open Container: $path"
  166. ::Browse::List::Reset
  167. if {[dict exists $::Browse::Containers {*}$path]} {
  168. set d [dict get $::Browse::Containers {*}$path]
  169. set type [dict get $d type]
  170. set containerName [dict get $d name]
  171. LOG "Container Name: $containerName"
  172. if {$::Browse::Refresh == 0} {
  173. if {[dict exists $d c]} {set ds $d; dict unset ds c} else {set ds $d}
  174. ::Widgets::Browse::Container {*}$ds
  175. } else {set ::Browse::Refresh 0}
  176. switch -glob -nocase -- $type {
  177. co* {
  178. # Container is a Container
  179. set ilist [::Browse::List::GetIcon ocontainer]
  180. set clist $containerName
  181. ::Browse::List::Add [list $clist] $ilist
  182. set ifCategory 0
  183. }
  184. ca* {
  185. # Container is a Category Container
  186. set ifCategory 1
  187. }
  188. default {set ifCategory 0}
  189. }
  190. if {[dict exists $d ccount] && [dict get $d ccount] > 0} {
  191. set c [dict get $d c]
  192. set clist ""; # Container Name List
  193. set ilist ""; # Icon List
  194. dict for {k v} $c {
  195. set type [dict get $v type]
  196. if {!$ifCategory} {
  197. lappend clist " [dict get $v name]"
  198. lappend ilist " [::Browse::List::GetIcon $type]"
  199. } else {
  200. lappend clist [dict get $v name]
  201. lappend ilist [::Browse::List::GetIcon $type]
  202. }
  203. }
  204. ::Browse::List::Add $clist $ilist
  205. }
  206. if {[dict exists $d icount] && [dict get $d icount] > 0} {
  207. set i [dict get $d i]
  208. set clist ""; # Item List
  209. set ilist ""; # Icon List
  210. dict for {k v} $i {
  211. set type [dict get $v type]
  212. if {!$ifCategory} {
  213. lappend clist " [dict get $v name]"
  214. lappend ilist " [::Browse::List::GetIcon $type]"
  215. } else {
  216. lappend clist [dict get $v name]
  217. lappend ilist [::Browse::List::GetIcon $type]
  218. }
  219. }
  220. ::Browse::List::Add $clist $ilist
  221. }
  222. }
  223. }
  224.  
  225. proc Add {name items params args} {
  226. ## ::Browse::Container::Add
  227. # Adds a Container to the Container List for Browsing and Selection
  228. # ::Browse::Container::Add "Apple Stations" $itemDict 0
  229. ## Parent Container Must Exist
  230. # Automatically increments the count values
  231. # Params will add params for the container such as ID's or anything else
  232. # into the params key
  233. LOG "::Browse::Container::Add -name $name -items $items -path $args"
  234. set args [string map {"c " ""} $args]
  235. set path c
  236. foreach p $args {
  237. lappend path $p
  238. if {![dict exists $::Browse::Containers {*}$path]} {
  239. LOG "Parent Container Doesn't Exist, Create it First!"
  240. LOG "Current Path: $path"
  241. return
  242. }
  243. lappend path c
  244. }
  245. LOG "Final Container Path: $path"
  246. set cPath [lrange $path 0 end-1]
  247. set check -1
  248. if {[catch {dict keys $items}]} {
  249. puts "ERROR: Items Not Dictionary"
  250. return
  251. }
  252. if {[dict exists $::Browse::Containers {*}$cPath ccount]} {
  253. set ccount [dict get $::Browse::Containers {*}$cPath ccount]
  254. if {$ccount > 0} {
  255. ## If Container Already Exists, Replace
  256. set d [dict get $::Browse::Containers {*}$cPath c]
  257. dict for {k v} $d {
  258. if {[dict exists $v name] && [dict get $v name] == $name} {
  259. set check $k
  260. break
  261. }
  262. }
  263. }
  264. if {$check == -1} {
  265. incr ccount
  266. dict set ::Browse::Containers {*}$cPath ccount $ccount
  267. set index [expr {$ccount - 1}]
  268. } else {
  269. set index $check
  270. }
  271. set icount [llength [dict keys $items]]
  272. dict set ::Browse::Containers {*}$path $index name $name
  273. dict set ::Browse::Containers {*}$path $index params [dict create {*}$params index $index]
  274. dict set ::Browse::Containers {*}$path $index type container
  275. if {![dict exists $::Browse::Containers {*}$path $index ccount]} {
  276. dict set ::Browse::Containers {*}$path $index ccount 0
  277. }
  278. dict set ::Browse::Containers {*}$path $index icount $icount
  279. if {$icount > 0} {
  280. dict set ::Browse::Containers {*}$path $index i $items
  281. }
  282. # Return the Path to the Procedure that Called it
  283. LOG "New Containers Value:\n$::Browse::Containers"
  284. return "$path $index"
  285. }
  286. }
  287.  
  288. proc Multiple {names args} {
  289. ## Creates Multiple Containers at path identified by $args
  290. # Containers Will be Empty, All Containers at path will be replaced
  291. # Example Call:
  292. # ::Browse::Container::Multiple [list Songs Artists Albums Genres Composers] 1
  293. LOG "::Browse::Container::Multiple $names $args"
  294. set path [::Browse::Container::Path $args]
  295. if {[dict exists $::Browse::Containers {*}$path]} {
  296. set ccount [llength $names]
  297. dict set ::Browse::Containers {*}$path ccount $ccount
  298. set i 0
  299. set c [dict create]
  300. foreach name $names {
  301. # 0 {name {Apple Stations} type container ccount 0 icount 0}
  302. dict set c $i [dict create name $name type container ccount 0 icount 0]
  303. incr i
  304. }
  305. dict set ::Browse::Containers {*}$path c $c
  306. LOG "New Container: [dict get $::Browse::Containers {*}$path]"
  307. } else {
  308. LOG "ERROR: Given Path $path Does Not Exist"
  309. }
  310. }
  311.  
  312. proc lappend {containers args} {
  313. ## Adds the provided $containers to the path identified by $args
  314. # Index provided for items will be ignored and replaced by proper
  315. # values. No check will be done so be careful not to provide
  316. # duplicate data
  317. set path [::Browse::Container::Path $args]
  318. if {[dict exists $::Browse::Containers {*}$path]} {
  319. set ccount [dict get $::Browse::Containers {*}$path ccount]
  320. dict for {k v} $items {
  321. dict set ::Browse::Containers {*}$path c $ccount $v
  322. incr ccount
  323. }
  324. dict set ::Browse::Containers {*}$path ccount $ccount
  325. LOG "New Container: [dict get $::Browse::Containers {*}$path]"
  326. } else {
  327. LOG "ERROR: Given Path $path Does Not Exist"
  328. }
  329.  
  330. }
  331.  
  332. proc Replace {containers args} {
  333. # Replaces Containers at Location Specified by args - should be
  334. # properly formatted containers and items inside of them (if applicable)
  335. # Will automatically adjust ccount and any items in the container
  336. # will be left alone
  337. set args [string map {"c " ""} $args]
  338. set path c
  339. foreach p $args {
  340. lappend path $p
  341. if {![dict exists $::Browse::Containers {*}$path]} {
  342. LOG "Parent Container Doesn't Exist, Create it First!"
  343. LOG "Current Path: $path"
  344. return
  345. }
  346. lappend path c
  347. }
  348. LOG "Final Container Path: $path"
  349. set cPath [lrange $path 0 end-1]
  350. set check -1
  351. if {[catch {dict keys $containers}]} {
  352. puts "ERROR: Containers Not Dictionary"
  353. return
  354. }
  355. if {[dict exists $::Browse::Containers {*}$cPath ccount]} {
  356. set ccount [dict get $::Browse::Containers {*}$cPath ccount]
  357. if {$ccount > 0} {LOG "Replacing Previous Containers"}
  358. }
  359. set ccount [llength [dict keys $containers]]
  360. dict set ::Browse::Containers {*}$cPath ccount $ccount
  361. dict set ::Browse::Containers {*}$path $containers
  362. LOG "Containers at $path Replaced!"
  363. LOG "[dict get $::Browse::Containers {*}$cPath]"
  364. }
  365.  
  366.  
  367. proc GetIndex {name args} {
  368. # Get the Index of the Container at the Given Path by Name
  369. # ::Browse::Container::GetIndex "Apple Stations" 0
  370. # -> 0 c 0
  371. }
  372.  
  373. proc GetParent {args} {
  374. ## Get the Parent Container of the Container at args
  375. # If Left Blank, Returns Parent of Current Container
  376. if {$args == ""} {
  377. set path $::Browse::CurrentContainer
  378. } else {
  379. set path $args
  380. }
  381. set parent [lrange $path 0 end-2]
  382. puts "Parent Container: $parent"
  383. set name [dict get $::Browse::Containers {*}$parent name]
  384. puts "Parent Name: $name"
  385. if {$name == "Root"} {
  386. LOG "Already at Root Level Returning $path"
  387. set rootPath [string map {"c " ""} $args]
  388. return $rootPath
  389. } else {
  390. set parentPath [string map {"c " ""} $parent]
  391. return $parentPath
  392. }
  393. }
  394.  
  395. proc Path {args} {
  396. set args [string map {"c " ""} $args]
  397. set path c
  398. foreach p {*}$args {
  399. lappend path $p
  400. if {![dict exists $::Browse::Containers {*}$path]} {
  401. puts "Parent Container Doesn't Exist, Create it First!"
  402. puts "Current Path: $path"
  403. return
  404. }
  405. lappend path c
  406. }
  407. if {[lindex $path end] eq "c"} {set path [lrange $path 0 end-1]}
  408. return $path
  409. }
  410. }
  411.  
  412. namespace eval Item {
  413.  
  414. proc Send {args} {
  415. ## Sends the Item to the System Script to Be Executed
  416. LOG "::Browse::Item::Send $args"
  417. ::device_proc "::ATV::Control::PlayItem $args"
  418. }
  419.  
  420. proc Open {args} {
  421. ## Open an Item at the Given Path
  422. # ::Browse::Item::Open 0 0 2 <- Item 3 in Container 0 -> 0
  423. LOG "::Browse::Item::Open $args"
  424. set args [string map {"c " ""} $args]
  425. set itemIndex [lindex $args end]
  426. set containerPath [lrange $args 0 end-1]
  427. set cPath [::Browse::Container::Path $containerPath]
  428. if {[dict exists $::Browse::Containers {*}$cPath i $itemIndex]} {
  429. ## Item Exists at Given Path!
  430. set d [dict get $::Browse::Containers {*}$cPath i $itemIndex]
  431. ::Browse::Item::Send $d
  432. }
  433. }
  434.  
  435. proc Add {type name id params args} {
  436. ## Adds a single Item to the Given Container found at $args
  437. LOG "::Browse::Item::Add $type $name $id $args"
  438. set path [::Browse::Container::Path $args]
  439. if {[dict exists $::Browse::Containers {*}$path]} {
  440. set icount [dict get $::Browse::Containers {*}$path icount]
  441. incr icount
  442. dict set ::Browse::Containers {*}$path icount $icount
  443. set index [expr {$icount - 1}]
  444. dict set ::Browse::Containers {*}$path i $index \
  445. [dict create type $type name $name id $id params $params]
  446. LOG "New Container: [dict get $::Browse::Containers {*}$path]"
  447. } else {
  448. LOG "ERROR: Given Path $path Does Not Exist"
  449. }
  450. }
  451.  
  452. proc Multiple {items args} {
  453. ## Adds Multiple Items to the Given Container found at $args
  454. # Items Must Be Formatted as a List of Items Properly Formatted. Their Index
  455. # will be converted automatically.
  456. set path [::Browse::Container::Path $args]
  457. if {[dict exists $::Browse::Containers {*}$path]} {
  458. set icount [dict get $::Browse::Containers {*}$path icount]
  459. dict for {k v} $items {
  460. dict set ::Browse::Containers {*}$path i $icount $v
  461. incr icount
  462. }
  463. dict set ::Browse::Containers {*}$path icount $icount
  464. LOG "New Container: [dict get $::Browse::Containers {*}$path]"
  465. } else {
  466. LOG "ERROR: Given Path $path Does Not Exist"
  467. }
  468. }
  469.  
  470. proc Replace {items args} {
  471. ## Replaces Items at $args with $items
  472. # The item dict should be properly formatted
  473. # 0 {type "Item Type" name "Item Name" id "ID"} 1 ...
  474. LOG "::Browse::Item::Multiple $items $args"
  475. set path [::Browse::Container::Path $args]
  476. if {[catch {dict keys $items}]} {LOG "$items is not a Dictionary"; return}
  477. if {[dict exists $::Browse::Containers {*}$path]} {
  478. set icount [llength [dict keys $items]]
  479. dict set ::Browse::Containers {*}$path icount $icount
  480. dict set ::Browse::Containers {*}$path i $items
  481. LOG "New Container: [dict get $::Browse::Containers {*}$path]"
  482. } else {
  483. LOG "ERROR: Given Path $path Does Not Exist"
  484. }
  485. }
  486. }
  487.  
  488. namespace eval List {
  489. proc Reset {} {
  490. ::clear_list "browse_list"
  491. }
  492.  
  493. proc Select {index} {
  494. # User Selects an Item in the List
  495. LOG "::Browse::List::Select $index"
  496. set c [dict get $::Browse::Containers {*}$::Browse::CurrentContainer]
  497. dict with c {}
  498. switch -glob -- $type {
  499. co* {
  500. # Browsing Container
  501. if {$index == 0} {
  502. # Navigate Back One
  503. ::Browse::Container::Back
  504. return
  505. } else {
  506. set index [expr {$index - 1}]
  507. }
  508. }
  509. ca* {
  510. # Browsing Category
  511. }
  512. default {
  513. LOG "ERROR: Browsing Unspecified Category - $type!"
  514. return
  515. }
  516. }
  517. set ifItem 0
  518. if {$ccount > 0} {
  519. set cindex [expr {$ccount - 1}]
  520. if {$index > $cindex} {
  521. set ifItem 1
  522. } else {
  523. LOG "Container Number: $index"
  524. ::Browse::Container::Open {*}$::Browse::CurrentContainer $index
  525. }
  526. } else {set ifItem 1}
  527. if {$ifItem && $icount > 0} {
  528. set index [expr {$index - $ccount}]
  529. LOG "Item Number: $index"
  530. ::Browse::Item::Open {*}$::Browse::CurrentContainer $index
  531. }
  532. }
  533.  
  534. proc Add {l i} {
  535. ## List Containers
  536. ::add_to_list "browse_list" $l $i
  537. }
  538.  
  539. proc GetIcon {t} {
  540. # Set the Icon to Return for Given Item or Container Types
  541. switch -nocase -glob -- $t {
  542. So* -
  543. St* {return s; # Station }
  544. Co* {return $; # Container }
  545. Oc* {return %; # Ocontainer (Open Container)}
  546. }
  547. }
  548. }
  549. }
  550.  
  551. if {$testing == 1} {
  552. proc LOG {t} {puts $t}
  553. namespace eval Widgets {
  554. namespace eval Meta {
  555. proc BrowseTitle {args} {puts "Simulating Browse Title to $args"}
  556. }
  557.  
  558. namespace eval Browse {
  559. proc Container {args} {puts "User is Browsing Container $args"}
  560. }
  561. }
  562.  
  563. proc add_to_list {args} {puts "Simulating Add to List $args"}
  564.  
  565. proc clear_list {args} {puts "Simulating Clear List $args"}
  566. }