_osd_menu.tcl 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837
  1. namespace eval osd_menu {
  2. set_help_text main_menu_open "Show the OSD menu."
  3. set_help_text main_menu_close "Remove the OSD menu."
  4. set_help_text main_menu_toggle "Toggle the OSD menu."
  5. variable is_dingux [string match dingux "[openmsx_info platform]"]
  6. variable scaling_available [expr {[lindex [lindex [openmsx_info setting scale_factor] 2] 1] > 1}]
  7. proc get_optional {dict_name key default} {
  8. upvar $dict_name d
  9. expr {[dict exists $d $key] ? [dict get $d $key] : $default}
  10. }
  11. proc set_optional {dict_name key value} {
  12. upvar $dict_name d
  13. if {![dict exists $d $key]} {
  14. dict set d $key $value
  15. }
  16. }
  17. variable menulevels 0
  18. proc push_menu_info {} {
  19. variable menulevels
  20. incr menulevels 1
  21. set levelname "menuinfo_$menulevels"
  22. variable $levelname
  23. set $levelname [uplevel {dict create \
  24. name $name lst $lst menu_len $menu_len presentation $presentation \
  25. menutexts $menutexts selectinfo $selectinfo selectidx $selectidx \
  26. scrollidx $scrollidx on_close $on_close}]
  27. }
  28. proc peek_menu_info {} {
  29. variable menulevels
  30. uplevel upvar #0 osd_menu::menuinfo_$menulevels menuinfo
  31. }
  32. proc set_selectidx {value} {
  33. peek_menu_info
  34. dict set menuinfo selectidx $value
  35. }
  36. proc set_scrollidx {value} {
  37. peek_menu_info
  38. dict set menuinfo scrollidx $value
  39. }
  40. proc menu_create {menudef} {
  41. variable menulevels
  42. variable default_bg_color
  43. variable default_text_color
  44. variable default_select_color
  45. variable default_header_text_color
  46. set name "menu[expr {$menulevels + 1}]"
  47. set defactions [get_optional menudef "actions" ""]
  48. set bgcolor [get_optional menudef "bg-color" $default_bg_color]
  49. set deftextcolor [get_optional menudef "text-color" $default_text_color]
  50. set selectcolor [get_optional menudef "select-color" $default_select_color]
  51. set deffontsize [get_optional menudef "font-size" 12]
  52. set deffont [get_optional menudef "font" "skins/Vera.ttf.gz"]
  53. set bordersize [get_optional menudef "border-size" 0]
  54. set on_open [get_optional menudef "on-open" ""]
  55. set on_close [get_optional menudef "on-close" ""]
  56. osd create rectangle $name -scaled true -rgba $bgcolor -clip true \
  57. -borderrgba 0x000000ff -bordersize 0.5
  58. set y $bordersize
  59. set selectinfo [list]
  60. set menutexts [list]
  61. foreach itemdef [dict get $menudef items] {
  62. set selectable [get_optional itemdef "selectable" true]
  63. incr y [get_optional itemdef "pre-spacing" 0]
  64. set fontsize [get_optional itemdef "font-size" $deffontsize]
  65. set font [get_optional itemdef "font" $deffont]
  66. set textcolor [expr {$selectable
  67. ? [get_optional itemdef "text-color" $deftextcolor]
  68. : [get_optional itemdef "text-color" $default_header_text_color]}]
  69. set actions [get_optional itemdef "actions" ""]
  70. set on_select [get_optional itemdef "on-select" ""]
  71. set on_deselect [get_optional itemdef "on-deselect" ""]
  72. set textid "${name}.item${y}"
  73. set text [dict get $itemdef text]
  74. lappend menutexts $textid $text
  75. osd create text $textid -font $font -size $fontsize \
  76. -rgba $textcolor -x $bordersize -y $y
  77. if {$selectable} {
  78. set allactions [concat $defactions $actions]
  79. lappend selectinfo [list $y $fontsize $allactions $on_select $on_deselect]
  80. }
  81. incr y $fontsize
  82. incr y [get_optional itemdef "post-spacing" 0]
  83. }
  84. set width [dict get $menudef width]
  85. set height [expr {$y + $bordersize}]
  86. set xpos [get_optional menudef "xpos" [expr {(320 - $width) / 2}]]
  87. set ypos [get_optional menudef "ypos" [expr {(240 - $height) / 2}]]
  88. osd configure $name -x $xpos -y $ypos -w $width -h $height
  89. set selw [expr {$width - 2 * $bordersize}]
  90. osd create rectangle "${name}.selection" -z -1 -rgba $selectcolor \
  91. -x $bordersize -w $selw
  92. set lst [get_optional menudef "lst" ""]
  93. set menu_len [get_optional menudef "menu_len" 0]
  94. if {[llength $lst] > $menu_len} {
  95. set startheight 0
  96. if {[llength $selectinfo] > 0} {
  97. # there are selectable items. Start the scrollbar
  98. # at the top of the first selectable item
  99. # (skipping headers and stuff)
  100. set startheight [lindex $selectinfo 0 0]
  101. }
  102. osd create rectangle "${name}.scrollbar" -z -1 -rgba 0x00000010 \
  103. -relx 1.0 -x -6 -w 6 -relh 1.0 -h -$startheight -y $startheight -borderrgba 0x00000070 -bordersize 0.5
  104. osd create rectangle "${name}.scrollbar.thumb" -z -1 -rgba $default_select_color \
  105. -relw 1.0 -w -2 -x 1
  106. }
  107. set presentation [get_optional menudef "presentation" ""]
  108. set selectidx 0
  109. set scrollidx 0
  110. push_menu_info
  111. uplevel #0 $on_open
  112. menu_on_select $selectinfo $selectidx
  113. menu_refresh_top
  114. menu_update_scrollbar
  115. }
  116. proc menu_update_scrollbar {} {
  117. peek_menu_info
  118. set name [dict get $menuinfo name]
  119. if {[osd exists ${name}.scrollbar]} {
  120. set menu_len [dict get $menuinfo menu_len]
  121. set scrollidx [dict get $menuinfo scrollidx]
  122. set selectidx [dict get $menuinfo selectidx]
  123. set totalitems [llength [dict get $menuinfo lst]]
  124. set height [expr {1.0*$menu_len/$totalitems}]
  125. set minheight 0.05 ;# TODO: derive from width of bar
  126. set height [expr {$height > $minheight ? $height : $minheight}]
  127. set pos [expr {1.0*($scrollidx+$selectidx)/($totalitems-1)}]
  128. # scale the pos to the usable range
  129. set pos [expr {$pos*(1.0-$height)}]
  130. osd configure "${name}.scrollbar.thumb" -relh $height -rely $pos
  131. }
  132. }
  133. proc menu_refresh_top {} {
  134. peek_menu_info
  135. foreach {osdid text} [dict get $menuinfo menutexts] {
  136. set cmd [list subst $text]
  137. osd configure $osdid -text [uplevel #0 $cmd]
  138. }
  139. set selectinfo [dict get $menuinfo selectinfo]
  140. if {[llength $selectinfo] == 0} return
  141. set selectidx [dict get $menuinfo selectidx ]
  142. lassign [lindex $selectinfo $selectidx] sely selh
  143. osd configure "[dict get $menuinfo name].selection" -y $sely -h $selh
  144. }
  145. proc menu_close_top {} {
  146. variable menulevels
  147. peek_menu_info
  148. menu_on_deselect [dict get $menuinfo selectinfo] [dict get $menuinfo selectidx]
  149. uplevel #0 [dict get $menuinfo on_close]
  150. osd destroy [dict get $menuinfo name]
  151. unset menuinfo
  152. incr menulevels -1
  153. if {$menulevels == 0} {
  154. menu_last_closed
  155. }
  156. }
  157. proc menu_close_all {} {
  158. variable menulevels
  159. while {$menulevels} {
  160. menu_close_top
  161. }
  162. }
  163. proc menu_setting {cmd_result} {
  164. menu_refresh_top
  165. }
  166. proc menu_updown {delta} {
  167. peek_menu_info
  168. set num [llength [dict get $menuinfo selectinfo]]
  169. if {$num == 0} return
  170. set old_idx [dict get $menuinfo selectidx]
  171. menu_reselect [expr {($old_idx + $delta) % $num}]
  172. }
  173. proc menu_reselect {new_idx} {
  174. peek_menu_info
  175. set selectinfo [dict get $menuinfo selectinfo]
  176. set old_idx [dict get $menuinfo selectidx]
  177. menu_on_deselect $selectinfo $old_idx
  178. set_selectidx $new_idx
  179. menu_on_select $selectinfo $new_idx
  180. menu_refresh_top
  181. }
  182. proc menu_on_select {selectinfo selectidx} {
  183. set on_select [lindex $selectinfo $selectidx 3]
  184. uplevel #0 $on_select
  185. }
  186. proc menu_on_deselect {selectinfo selectidx} {
  187. set on_deselect [lindex $selectinfo $selectidx 4]
  188. uplevel #0 $on_deselect
  189. }
  190. proc menu_action {button} {
  191. peek_menu_info
  192. set selectidx [dict get $menuinfo selectidx ]
  193. menu_action_idx $selectidx $button
  194. }
  195. proc menu_action_idx {idx button} {
  196. peek_menu_info
  197. set selectinfo [dict get $menuinfo selectinfo]
  198. set actions [lindex $selectinfo $idx 2]
  199. set_optional actions UP {osd_menu::menu_updown -1}
  200. set_optional actions DOWN {osd_menu::menu_updown 1}
  201. set_optional actions B {osd_menu::menu_close_top}
  202. set cmd [get_optional actions $button ""]
  203. uplevel #0 $cmd
  204. }
  205. proc get_mouse_coords {} {
  206. peek_menu_info
  207. set name [dict get $menuinfo name]
  208. set x 2; set y 2
  209. catch {lassign [osd info $name -mousecoord] x y}
  210. list $x $y
  211. }
  212. proc menu_get_mouse_idx {xy} {
  213. lassign $xy x y
  214. if {$x < 0 || 1 < $x || $y < 0 || 1 < $y} {return -1}
  215. peek_menu_info
  216. set name [dict get $menuinfo name]
  217. set yy [expr {$y * [osd info $name -h]}]
  218. set sel 0
  219. foreach i [dict get $menuinfo selectinfo] {
  220. lassign $i y h actions
  221. if {($y <= $yy) && ($yy < ($y + $h))} {
  222. return $sel
  223. }
  224. incr sel
  225. }
  226. return -1
  227. }
  228. proc menu_mouse_down {} {
  229. variable mouse_coord
  230. variable mouse_idx
  231. set mouse_coord [get_mouse_coords]
  232. set mouse_idx [menu_get_mouse_idx $mouse_coord]
  233. if {$mouse_idx != -1} {
  234. menu_reselect $mouse_idx
  235. }
  236. }
  237. proc menu_mouse_up {} {
  238. variable mouse_coord
  239. variable mouse_idx
  240. set mouse_coord [get_mouse_coords]
  241. set mouse_idx [menu_get_mouse_idx $mouse_coord]
  242. if {$mouse_idx != -1} {
  243. menu_action_idx $mouse_idx A
  244. }
  245. unset mouse_coord
  246. unset mouse_idx
  247. }
  248. proc menu_mouse_motion {} {
  249. variable mouse_coord
  250. variable mouse_idx
  251. if {![info exists mouse_coord]} return
  252. set new_mouse_coord [get_mouse_coords]
  253. set new_idx [menu_get_mouse_idx $new_mouse_coord]
  254. if {$new_idx != -1 && $new_idx != $mouse_idx} {
  255. menu_reselect $new_idx
  256. set mouse_coord $new_mouse_coord
  257. set mouse_idx $new_idx
  258. return
  259. }
  260. if {$mouse_idx != -1} {
  261. lassign $mouse_coord old_x old_y
  262. lassign $new_mouse_coord new_x new_y
  263. set delta_x [expr {$new_x - $old_x}]
  264. set delta_y [expr {$new_y - $old_y}]
  265. if {$delta_y > 0.1} {
  266. menu_action_idx $mouse_idx DOWN
  267. set mouse_coord $new_mouse_coord
  268. return
  269. } elseif {$delta_y < -0.1} {
  270. menu_action_idx $mouse_idx UP
  271. set mouse_coord $new_mouse_coord
  272. return
  273. } elseif {$delta_x > 0.1} {
  274. menu_action_idx $mouse_idx RIGHT
  275. set mouse_coord $new_mouse_coord
  276. return
  277. } elseif {$delta_x < -0.1} {
  278. menu_action_idx $mouse_idx LEFT
  279. set mouse_coord $new_mouse_coord
  280. return
  281. }
  282. }
  283. }
  284. user_setting create string osd_rom_path "OSD Rom Load Menu Last Known Path" $env(HOME)
  285. user_setting create string osd_disk_path "OSD Disk Load Menu Last Known Path" $env(HOME)
  286. user_setting create string osd_tape_path "OSD Tape Load Menu Last Known Path" $env(HOME)
  287. user_setting create string osd_hdd_path "OSD HDD Load Menu Last Known Path" $env(HOME)
  288. user_setting create string osd_ld_path "OSD LD Load Menu Last Known Path" $env(HOME)
  289. if {![file exists $::osd_rom_path] || ![file readable $::osd_rom_path]} {
  290. # revert to default (should always exist)
  291. unset ::osd_rom_path
  292. }
  293. if {![file exists $::osd_disk_path] || ![file readable $::osd_disk_path]} {
  294. # revert to default (should always exist)
  295. unset ::osd_disk_path
  296. }
  297. if {![file exists $::osd_tape_path] || ![file readable $::osd_tape_path]} {
  298. # revert to default (should always exist)
  299. unset ::osd_tape_path
  300. }
  301. if {![file exists $::osd_hdd_path] || ![file readable $::osd_hdd_path]} {
  302. # revert to default (should always exist)
  303. unset ::osd_hdd_path
  304. }
  305. if {![file exists $::osd_ld_path] || ![file readable $::osd_ld_path]} {
  306. # revert to default (should always exist)
  307. unset ::osd_ld_path
  308. }
  309. variable taperecordings_directory [file normalize $::env(OPENMSX_USER_DATA)/../taperecordings]
  310. proc main_menu_open {} {
  311. do_menu_open [create_main_menu]
  312. }
  313. proc do_menu_open {top_menu} {
  314. variable is_dingux
  315. # close console, because the menu interferes with it
  316. set ::console off
  317. # also remove other OSD controlled widgets (like the osd keyboard)
  318. if {[info exists ::osd_control::close]} {
  319. eval $::osd_control::close
  320. }
  321. # end tell how to close this widget
  322. namespace eval ::osd_control {set close ::osd_menu::main_menu_close}
  323. menu_create $top_menu
  324. set ::pause true
  325. # TODO make these bindings easier to customize
  326. bind -layer osd_menu "OSDcontrol UP PRESS" -repeat {osd_menu::menu_action UP }
  327. bind -layer osd_menu "OSDcontrol DOWN PRESS" -repeat {osd_menu::menu_action DOWN }
  328. bind -layer osd_menu "OSDcontrol LEFT PRESS" -repeat {osd_menu::menu_action LEFT }
  329. bind -layer osd_menu "OSDcontrol RIGHT PRESS" -repeat {osd_menu::menu_action RIGHT}
  330. bind -layer osd_menu "mouse button1 down" {osd_menu::menu_mouse_down}
  331. bind -layer osd_menu "mouse button1 up" {osd_menu::menu_mouse_up}
  332. bind -layer osd_menu "mouse button3 up" {osd_menu::menu_close_top}
  333. bind -layer osd_menu "mouse motion" {osd_menu::menu_mouse_motion}
  334. bind -layer osd_menu "mouse button4 down" {osd_menu::menu_action UP }
  335. bind -layer osd_menu "mouse button5 down" {osd_menu::menu_action DOWN }
  336. if {$is_dingux} {
  337. bind -layer osd_menu "keyb LCTRL" {osd_menu::menu_action A }
  338. bind -layer osd_menu "keyb LALT" {osd_menu::menu_action B }
  339. } else {
  340. bind -layer osd_menu "OSDcontrol A PRESS" {osd_menu::menu_action A }
  341. bind -layer osd_menu "OSDcontrol B PRESS" {osd_menu::menu_action B }
  342. # on Android, use BACK button to go back in menus
  343. bind -layer osd_menu "keyb BACK" {osd_menu::menu_action B }
  344. }
  345. activate_input_layer osd_menu -blocking
  346. }
  347. proc main_menu_close {} {
  348. menu_close_all
  349. }
  350. proc main_menu_toggle {} {
  351. variable menulevels
  352. if {$menulevels} {
  353. # there is at least one menu open, close it
  354. menu_close_all
  355. } else {
  356. # none open yet, open main menu
  357. main_menu_open
  358. }
  359. }
  360. proc menu_last_closed {} {
  361. variable is_dingux
  362. set ::pause false
  363. deactivate_input_layer osd_menu
  364. namespace eval ::osd_control {unset close}
  365. }
  366. proc prepare_menu_list {lst num menudef} {
  367. set execute [dict get $menudef execute]
  368. set header [dict get $menudef header]
  369. set item_extra [get_optional menudef item ""]
  370. set on_select [get_optional menudef on-select ""]
  371. set on_deselect [get_optional menudef on-deselect ""]
  372. set presentation [get_optional menudef presentation $lst]
  373. # 'assert': presentation should have same length as item list!
  374. if {[llength $presentation] != [llength $lst]} {
  375. error "Presentation should be of same length as item list!"
  376. }
  377. dict set menudef presentation $presentation
  378. lappend header "selectable" "false"
  379. set items [list $header]
  380. set lst_len [llength $lst]
  381. set menu_len [expr {$lst_len < $num ? $lst_len : $num}]
  382. for {set i 0} {$i < $menu_len} {incr i} {
  383. set actions [list "A" "osd_menu::list_menu_item_exec {$execute} $i"]
  384. if {$i == 0} {
  385. lappend actions "UP" "osd_menu::move_selection -1"
  386. }
  387. if {$i == ($menu_len - 1)} {
  388. lappend actions "DOWN" "osd_menu::move_selection 1"
  389. }
  390. lappend actions "LEFT" "osd_menu::move_selection -$menu_len"
  391. lappend actions "RIGHT" "osd_menu::move_selection $menu_len"
  392. set item [list "text" "\[osd_menu::list_menu_item_show $i\]" \
  393. "actions" $actions]
  394. if {$on_select ne ""} {
  395. lappend item "on-select" "osd_menu::list_menu_item_select $i $on_select"
  396. }
  397. if {$on_deselect ne ""} {
  398. lappend item "on-deselect" "osd_menu::list_menu_item_select $i $on_deselect"
  399. }
  400. lappend items [concat $item $item_extra]
  401. }
  402. dict set menudef items $items
  403. dict set menudef lst $lst
  404. dict set menudef menu_len $menu_len
  405. return $menudef
  406. }
  407. proc list_menu_item_exec {execute pos} {
  408. peek_menu_info
  409. {*}$execute [lindex [dict get $menuinfo lst] [expr {$pos + [dict get $menuinfo scrollidx]}]]
  410. }
  411. proc list_menu_item_show {pos} {
  412. peek_menu_info
  413. return [lindex [dict get $menuinfo presentation] [expr {$pos + [dict get $menuinfo scrollidx]}]]
  414. }
  415. proc list_menu_item_select {pos select_proc} {
  416. peek_menu_info
  417. $select_proc [lindex [dict get $menuinfo lst] [expr {$pos + [dict get $menuinfo scrollidx]}]]
  418. }
  419. proc move_selection {delta} {
  420. peek_menu_info
  421. set lst_last [expr {[llength [dict get $menuinfo lst]] - 1}]
  422. set scrollidx [dict get $menuinfo scrollidx]
  423. set selectidx [dict get $menuinfo selectidx]
  424. set old_itemidx [expr {$scrollidx + $selectidx}]
  425. set new_itemidx [expr {$old_itemidx + $delta}]
  426. if {$new_itemidx < 0} {
  427. # Before first element
  428. if {$old_itemidx == 0} {
  429. # if first element was already selected, wrap to last
  430. set new_itemidx $lst_last
  431. } else {
  432. # otherwise, clamp to first element
  433. set new_itemidx 0
  434. }
  435. } elseif {$new_itemidx > $lst_last} {
  436. # After last element
  437. if {$old_itemidx == $lst_last} {
  438. # if last element was already selected, wrap to first
  439. set new_itemidx 0
  440. } else {
  441. # otherwise clam to last element
  442. set new_itemidx $lst_last
  443. }
  444. }
  445. select_menu_idx $new_itemidx
  446. }
  447. proc select_menu_idx {itemidx} {
  448. peek_menu_info
  449. set menu_len [dict get $menuinfo menu_len]
  450. set scrollidx [dict get $menuinfo scrollidx]
  451. set selectidx [dict get $menuinfo selectidx]
  452. set selectinfo [dict get $menuinfo selectinfo]
  453. menu_on_deselect $selectinfo $selectidx
  454. set selectidx [expr {$itemidx - $scrollidx}]
  455. if {$selectidx < 0} {
  456. incr scrollidx $selectidx
  457. set selectidx 0
  458. } elseif {$selectidx >= $menu_len} {
  459. set selectidx [expr {$menu_len - 1}]
  460. set scrollidx [expr {$itemidx - $selectidx}]
  461. }
  462. set_selectidx $selectidx
  463. set_scrollidx $scrollidx
  464. menu_on_select $selectinfo $selectidx
  465. menu_refresh_top
  466. menu_update_scrollbar
  467. }
  468. proc select_menu_item {item} {
  469. peek_menu_info
  470. set index [lsearch -exact [dict get $menuinfo lst] $item]
  471. if {$index == -1} return
  472. select_menu_idx $index
  473. }
  474. #
  475. # definitions of menus
  476. #
  477. proc create_main_menu {} {
  478. set menu_def {
  479. font-size 10
  480. border-size 2
  481. width 160
  482. }
  483. lappend items { text "[openmsx_info version]"
  484. font-size 12
  485. post-spacing 6
  486. selectable false }
  487. if {[catch carta]} {; # example: Philips NMS 801
  488. lappend items { text "(No cartridge slot available...)"
  489. selectable false
  490. text-color 0x808080ff
  491. }
  492. } else {
  493. foreach slot [lrange [lsort [info command cart?]] 0 1] {
  494. set slot_str [string toupper [string index $slot end]]
  495. lappend items [list text "Load ROM... (slot $slot_str)" \
  496. actions [list A "osd_menu::menu_create \[osd_menu::menu_create_ROM_list \$::osd_rom_path $slot\]"]]
  497. }
  498. }
  499. if {[catch diska]} {
  500. lappend items { text "(No disk drives available...)"
  501. selectable false
  502. text-color 0x808080ff
  503. }
  504. } else {
  505. foreach drive [lrange [lsort [info command disk?]] 0 1] {
  506. set drive_str [string toupper [string index $drive end]]
  507. lappend items [list text "Insert Disk... (drive $drive_str)" \
  508. actions [list A "osd_menu::menu_create \[osd_menu::menu_create_disk_list \$::osd_disk_path $drive\]"]]
  509. }
  510. }
  511. if {[info command hda] ne ""} {; # only exists when hard disk extension available
  512. foreach drive [lrange [lsort [info command hd?]] 0 1] {
  513. set drive_str [string toupper [string index $drive end]]
  514. lappend items [list text "Change HD/SD image... (drive $drive_str)" \
  515. actions [list A "osd_menu::menu_create \[osd_menu::menu_create_hdd_list \$::osd_hdd_path $drive\]"]]
  516. }
  517. }
  518. if {[info command laserdiscplayer] ne ""} {; # only exists on some Pioneers
  519. lappend items { text "Load LaserDisc..."
  520. actions { A { osd_menu::menu_create [osd_menu::menu_create_ld_list $::osd_ld_path]} }
  521. }
  522. }
  523. if {[catch "machine_info connector cassetteport"]} {; # example: turboR
  524. lappend items { text "(No cassette port present...)"
  525. selectable false
  526. text-color 0x808080ff
  527. post-spacing 3
  528. }
  529. } else {
  530. lappend items { text "Set Tape..."
  531. actions { A { osd_menu::menu_create [osd_menu::menu_create_tape_list $::osd_tape_path]} }
  532. post-spacing 3 }
  533. }
  534. lappend items { text "Save State..."
  535. actions { A { osd_menu::menu_create [osd_menu::menu_create_save_state] }}}
  536. lappend items { text "Load State..."
  537. actions { A { osd_menu::menu_create [osd_menu::menu_create_load_state] }}
  538. post-spacing 3 }
  539. lappend items { text "Hardware..."
  540. actions { A { osd_menu::menu_create $osd_menu::hardware_menu }}
  541. post-spacing 3 }
  542. lappend items { text "Misc Settings..."
  543. actions { A { osd_menu::menu_create $osd_menu::misc_setting_menu }}}
  544. lappend items { text "Sound Settings..."
  545. actions { A { osd_menu::menu_create $osd_menu::sound_setting_menu }}}
  546. lappend items { text "Video Settings..."
  547. actions { A { osd_menu::menu_create [osd_menu::create_video_setting_menu] }}
  548. post-spacing 3 }
  549. lappend items { text "Advanced..."
  550. actions { A { osd_menu::menu_create $osd_menu::advanced_menu }}
  551. post-spacing 10 }
  552. lappend items { text "Reset MSX"
  553. actions { A { reset; osd_menu::menu_close_all }}}
  554. lappend items { text "Exit openMSX"
  555. actions { A quitmenu::quit_menu }}
  556. dict set menu_def items $items
  557. return $menu_def
  558. }
  559. set misc_setting_menu {
  560. font-size 8
  561. border-size 2
  562. width 150
  563. xpos 100
  564. ypos 120
  565. items {{ text "Misc Settings"
  566. font-size 10
  567. post-spacing 6
  568. selectable false }
  569. { text "Speed: $speed"
  570. actions { LEFT { osd_menu::menu_setting [incr speed -1] }
  571. RIGHT { osd_menu::menu_setting [incr speed 1] }}}
  572. { text "Minimal Frameskip: $minframeskip"
  573. actions { LEFT { osd_menu::menu_setting [incr minframeskip -1] }
  574. RIGHT { osd_menu::menu_setting [incr minframeskip 1] }}}
  575. { text "Maximal Frameskip: $maxframeskip"
  576. actions { LEFT { osd_menu::menu_setting [incr maxframeskip -1] }
  577. RIGHT { osd_menu::menu_setting [incr maxframeskip 1] }}}}}
  578. set resampler_desc [dict create fast "fast (but low quality)" blip "blip (good speed/quality)" hq "hq (best but slow on Android)"]
  579. set sound_setting_menu {
  580. font-size 8
  581. border-size 2
  582. width 180
  583. xpos 100
  584. ypos 120
  585. items {{ text "Sound Settings"
  586. font-size 10
  587. post-spacing 6
  588. selectable false }
  589. { text "Volume: $master_volume"
  590. actions { LEFT { osd_menu::menu_setting [incr master_volume -5] }
  591. RIGHT { osd_menu::menu_setting [incr master_volume 5] }}}
  592. { text "Mute: $mute"
  593. actions { LEFT { osd_menu::menu_setting [cycle_back mute] }
  594. RIGHT { osd_menu::menu_setting [cycle mute] }}}
  595. { text "Individual Sound Device Settings..."
  596. actions { A { osd_menu::menu_create [osd_menu::menu_create_sound_device_list]}}}
  597. { text "Resampler: [osd_menu::get_resampler_presentation $resampler]"
  598. actions { LEFT { osd_menu::menu_setting [cycle_back resampler] }
  599. RIGHT { osd_menu::menu_setting [cycle resampler] }}}}}
  600. set horizontal_stretch_desc [dict create 320.0 "none (large borders)" 288.0 "a bit more than all border pixels" 284.0 "all border pixels" 280.0 "a bit less than all border pixels" 272.0 "realistic" 256.0 "no borders at all"]
  601. proc menu_create_sound_device_list {} {
  602. set menu_def {
  603. execute menu_sound_device_select_exec
  604. font-size 8
  605. border-size 2
  606. width 200
  607. xpos 110
  608. ypos 130
  609. header { text "Select Sound Chip"
  610. font-size 10
  611. post-spacing 6 }}
  612. set items [machine_info sounddevice]
  613. return [prepare_menu_list $items 5 $menu_def]
  614. }
  615. proc menu_sound_device_select_exec {item} {
  616. menu_create [create_sound_device_settings_menu $item]
  617. select_menu_item $item
  618. }
  619. proc create_sound_device_settings_menu {device} {
  620. set ypos 140
  621. set menu_def [list \
  622. font-size 8 \
  623. border-size 2 \
  624. width 210 \
  625. xpos 120 \
  626. ypos $ypos]
  627. lappend items [list text "$device Settings" \
  628. font-size 10 \
  629. post-spacing 6 \
  630. selectable false]
  631. # volume and balance
  632. foreach aspect [list volume balance] {
  633. set var_name ::${device}_${aspect}
  634. set item [list]
  635. lappend item "text"
  636. set first [string range $aspect 0 0]
  637. set rest [string range $aspect 1 end]
  638. set first [string toupper $first]
  639. set capped_aspect "${first}${rest}"
  640. lappend item "$capped_aspect: \[[list set $var_name]]"
  641. lappend item "actions"
  642. set actions [list]
  643. lappend actions "LEFT"
  644. lappend actions "osd_menu::menu_setting \[[list incr $var_name -5]]"
  645. lappend actions "RIGHT"
  646. lappend actions "osd_menu::menu_setting \[[list incr $var_name 5]]"
  647. lappend item $actions
  648. lappend items $item
  649. }
  650. # channel mute
  651. set channel_count [soundchip_utils::get_num_channels $device]
  652. for {set channel 1} {$channel <= $channel_count} {incr channel} {
  653. set chmute_var_name ${device}_ch${channel}_mute
  654. set item [list]
  655. lappend item "text"
  656. set pretext ""
  657. if {$channel_count > 1} {
  658. set pretext "Channel $channel "
  659. }
  660. lappend item "${pretext}Mute: \[[list set $chmute_var_name]]"
  661. lappend item "actions"
  662. set actions [list]
  663. lappend actions "LEFT"
  664. lappend actions "osd_menu::menu_setting \[[list cycle_back $chmute_var_name]]"
  665. lappend actions "RIGHT"
  666. lappend actions "osd_menu::menu_setting \[[list cycle $chmute_var_name]]"
  667. lappend item $actions
  668. lappend items $item
  669. }
  670. # adjust menu position for longer lists
  671. # TODO: make this less magic
  672. if {$channel_count > 8} {;# more won't fit
  673. dict set menu_def ypos [expr {$ypos - round(($channel_count - 8) * ($ypos - 10)/16)}]
  674. }
  675. dict set menu_def items $items
  676. return $menu_def
  677. }
  678. proc create_video_setting_menu {} {
  679. variable scaling_available
  680. set menu_def {
  681. font-size 8
  682. border-size 2
  683. width 210
  684. xpos 100
  685. ypos 110
  686. }
  687. lappend items { text "Video Settings"
  688. font-size 10
  689. post-spacing 6
  690. selectable false }
  691. if {[expr {[lindex [lindex [openmsx_info setting videosource] 2] 1] > 1}]} {
  692. lappend items { text "Video source: $videosource"
  693. actions { LEFT { osd_menu::menu_setting [cycle_back videosource] }
  694. RIGHT { osd_menu::menu_setting [cycle videosource] }}
  695. post-spacing 6}
  696. }
  697. if {$scaling_available} {
  698. lappend items { text "Scaler: $scale_algorithm"
  699. actions { LEFT { osd_menu::menu_setting [cycle_back scale_algorithm] }
  700. RIGHT { osd_menu::menu_setting [cycle scale_algorithm] }}}
  701. # only add scale factor setting if it can actually be changed
  702. set scale_minmax [lindex [openmsx_info setting scale_factor] 2]
  703. if {[expr {[lindex $scale_minmax 0] != [lindex $scale_minmax 1]}]} {
  704. lappend items { text "Scale Factor: ${scale_factor}x"
  705. actions { LEFT { osd_menu::menu_setting [incr scale_factor -1] }
  706. RIGHT { osd_menu::menu_setting [incr scale_factor 1] }}}
  707. }
  708. }
  709. lappend items { text "Horizontal Stretch: [osd_menu::get_horizontal_stretch_presentation $horizontal_stretch]"
  710. actions { A { osd_menu::menu_create [osd_menu::menu_create_stretch_list]; osd_menu::select_menu_item $horizontal_stretch }}
  711. post-spacing 6 }
  712. if {$scaling_available} {
  713. lappend items { text "Scanline: $scanline%"
  714. actions { LEFT { osd_menu::menu_setting [incr scanline -1] }
  715. RIGHT { osd_menu::menu_setting [incr scanline 1] }}}
  716. lappend items { text "Blur: $blur%"
  717. actions { LEFT { osd_menu::menu_setting [incr blur -1] }
  718. RIGHT { osd_menu::menu_setting [incr blur 1] }}}
  719. }
  720. if {$::renderer eq "SDLGL-PP"} {
  721. lappend items { text "Glow: $glow%"
  722. actions { LEFT { osd_menu::menu_setting [incr glow -1] }
  723. RIGHT { osd_menu::menu_setting [incr glow 1] }}}
  724. lappend items { text "Display Deform: $display_deform"
  725. actions { LEFT { osd_menu::menu_setting [cycle_back display_deform] }
  726. RIGHT { osd_menu::menu_setting [cycle display_deform] }}}
  727. }
  728. lappend items { text "Noise: $noise%"
  729. actions { LEFT { osd_menu::menu_setting [set noise [expr $noise - 1]] }
  730. RIGHT { osd_menu::menu_setting [set noise [expr $noise + 1]] }}
  731. post-spacing 6}
  732. lappend items { text "Enforce VDP Sprites-per-line Limit: $limitsprites"
  733. actions { LEFT { osd_menu::menu_setting [cycle_back limitsprites] }
  734. RIGHT { osd_menu::menu_setting [cycle limitsprites] }}}
  735. dict set menu_def items $items
  736. return $menu_def
  737. }
  738. set hardware_menu {
  739. font-size 8
  740. border-size 2
  741. width 175
  742. xpos 100
  743. ypos 120
  744. items {{ text "Hardware"
  745. font-size 10
  746. post-spacing 6
  747. selectable false }
  748. { text "Change Machine..."
  749. actions { A { osd_menu::menu_create [osd_menu::menu_create_load_machine_list]; catch { osd_menu::select_menu_item [machine_info config_name]} }}}
  750. { text "Set Current Machine as Default"
  751. actions { A { set ::default_machine [machine_info config_name]; osd_menu::menu_close_top }}}
  752. { text "Extensions..."
  753. actions { A { osd_menu::menu_create $osd_menu::extensions_menu }}}
  754. { text "Connectors..."
  755. actions { A { osd_menu::menu_create [osd_menu::menu_create_connectors_list] }}}
  756. }}
  757. set extensions_menu {
  758. font-size 8
  759. border-size 2
  760. width 175
  761. xpos 100
  762. ypos 120
  763. items {{ text "Extensions"
  764. font-size 10
  765. post-spacing 6
  766. selectable false }
  767. { text "Add..."
  768. actions { A { osd_menu::menu_create [osd_menu::menu_create_extensions_list] }}}
  769. { text "Remove..."
  770. actions { A { osd_menu::menu_create [osd_menu::menu_create_plugged_extensions_list] }}}}}
  771. set advanced_menu {
  772. font-size 8
  773. border-size 2
  774. width 175
  775. xpos 100
  776. ypos 120
  777. items {{ text "Advanced"
  778. font-size 10
  779. post-spacing 6
  780. selectable false }
  781. { text "Manage Running Machines..."
  782. actions { A { osd_menu::menu_create $osd_menu::running_machines_menu }}}
  783. { text "Toys and Utilities..."
  784. actions { A { osd_menu::menu_create [osd_menu::menu_create_toys_list] }}}}}
  785. set running_machines_menu {
  786. font-size 8
  787. border-size 2
  788. width 175
  789. xpos 100
  790. ypos 120
  791. items {{ text "Manage Running Machines"
  792. font-size 10
  793. post-spacing 6
  794. selectable false }
  795. { text "Select Running Machine Tab: [utils::get_machine_display_name]"
  796. actions { A { osd_menu::menu_create [osd_menu::menu_create_running_machine_list] }}}
  797. { text "New Running Machine Tab"
  798. actions { A { osd_menu::menu_create [osd_menu::menu_create_load_machine_list "add"] }}}
  799. { text "Close Current Machine Tab"
  800. actions { A { set old_active_machine [activate_machine]; cycle_machine; delete_machine $old_active_machine }}}}}
  801. proc menu_create_running_machine_list {} {
  802. set menu_def {
  803. execute menu_machine_tab_select_exec
  804. font-size 8
  805. border-size 2
  806. width 200
  807. xpos 110
  808. ypos 130
  809. header { text "Select Running Machine"
  810. font-size 10
  811. post-spacing 6 }}
  812. set items [utils::get_ordered_machine_list]
  813. set presentation [list]
  814. foreach i $items {
  815. if {[activate_machine] eq $i} {
  816. set postfix_text "current"
  817. } else {
  818. set postfix_text [utils::get_machine_time $i]
  819. }
  820. lappend presentation [format "%s (%s)" [utils::get_machine_display_name ${i}] $postfix_text]
  821. }
  822. lappend menu_def presentation $presentation
  823. return [prepare_menu_list $items 5 $menu_def]
  824. }
  825. proc menu_machine_tab_select_exec {item} {
  826. menu_close_top
  827. activate_machine $item
  828. }
  829. proc get_resampler_presentation { value } {
  830. if {[dict exists $osd_menu::resampler_desc $value]} {
  831. return [dict get $osd_menu::resampler_desc $value]
  832. } else {
  833. return $value
  834. }
  835. }
  836. proc get_horizontal_stretch_presentation { value } {
  837. if {[dict exists $osd_menu::horizontal_stretch_desc $value]} {
  838. return [dict get $osd_menu::horizontal_stretch_desc $value]
  839. } else {
  840. return "custom: $::horizontal_stretch"
  841. }
  842. }
  843. proc menu_create_stretch_list {} {
  844. set menu_def [list \
  845. execute menu_stretch_exec \
  846. font-size 8 \
  847. border-size 2 \
  848. width 150 \
  849. xpos 110 \
  850. ypos 130 \
  851. header { text "Select Horizontal Stretch:"
  852. font-size 10
  853. post-spacing 6 }]
  854. set items [list]
  855. set presentation [list]
  856. set values [dict keys $osd_menu::horizontal_stretch_desc]
  857. if {$::horizontal_stretch ni $values} {
  858. lappend values $::horizontal_stretch
  859. }
  860. foreach value $values {
  861. lappend items $value
  862. lappend presentation [osd_menu::get_horizontal_stretch_presentation $value]
  863. }
  864. lappend menu_def presentation $presentation
  865. return [prepare_menu_list $items 6 $menu_def]
  866. }
  867. proc menu_stretch_exec {value} {
  868. set ::horizontal_stretch $value
  869. menu_close_top
  870. menu_refresh_top
  871. }
  872. # Returns list of machines/extensions, but try to filter out duplicates caused
  873. # by symlinks (e.g. turbor.xml -> Panasonic_FS-A1GT.xml). What this does not
  874. # catch is a symlink in the systemdir (so link also pointing to the systemdir)
  875. # and a similarly named file in the userdir. This situation does occur on my
  876. # development setup, but it shouldn't happen for regular users.
  877. proc get_filtered_configs {type} {
  878. set result [list]
  879. set configs [list]
  880. foreach t [openmsx_info $type] {
  881. # try both <name>.xml and <name>/hardwareconfig.xml
  882. set conf [data_file $type/$t.xml]
  883. if {![file exists $conf]} {
  884. set conf [data_file $type/$t/hardwareconfig.xml]
  885. }
  886. # follow symlink (on platforms that support links)
  887. catch {
  888. set conf [file join [file dirname $conf]
  889. [file readlink $conf]]
  890. }
  891. # only add if the (possibly resolved link) hasn't been seen before
  892. if {$conf ni $configs} {
  893. lappend configs $conf
  894. lappend result $t
  895. }
  896. }
  897. return $result
  898. }
  899. proc menu_create_load_machine_list {{mode "replace"}} {
  900. if {$mode eq "replace"} {
  901. set proc_to_exec osd_menu::menu_load_machine_exec_replace
  902. } elseif {$mode eq "add"} {
  903. set proc_to_exec osd_menu::menu_load_machine_exec_add
  904. } else {
  905. error "Undefined mode: $mode"
  906. }
  907. set menu_def [list \
  908. execute $proc_to_exec \
  909. font-size 8 \
  910. border-size 2 \
  911. width 200 \
  912. xpos 110 \
  913. ypos 130 \
  914. header { text "Select Machine to Run"
  915. font-size 10
  916. post-spacing 6 }]
  917. set items [get_filtered_configs machines]
  918. foreach i $items {
  919. set extra_info ""
  920. if {$i eq $::default_machine} {
  921. set extra_info " (default)"
  922. }
  923. lappend presentation "[utils::get_machine_display_name_by_config_name $i]$extra_info"
  924. }
  925. set items_sorted [list]
  926. set presentation_sorted [list]
  927. foreach i [lsort -dictionary -indices $presentation] {
  928. lappend presentation_sorted [lindex $presentation $i]
  929. lappend items_sorted [lindex $items $i]
  930. }
  931. lappend menu_def presentation $presentation_sorted
  932. return [prepare_menu_list $items_sorted 10 $menu_def]
  933. }
  934. proc menu_load_machine_exec_replace {item} {
  935. if {[catch {machine $item} errorText]} {
  936. osd::display_message $errorText error
  937. } else {
  938. menu_close_all
  939. }
  940. }
  941. proc menu_load_machine_exec_add {item} {
  942. set id [create_machine]
  943. set err [catch {${id}::load_machine $item} error_result]
  944. if {$err} {
  945. delete_machine $id
  946. osd::display_message "Error starting [utils::get_machine_display_name_by_config_name $item]: $error_result" error
  947. } else {
  948. menu_close_top
  949. activate_machine $id
  950. }
  951. }
  952. proc menu_create_extensions_list {} {
  953. set menu_def {
  954. execute menu_add_extension_exec
  955. font-size 8
  956. border-size 2
  957. width 200
  958. xpos 110
  959. ypos 130
  960. header { text "Select Extension to Add"
  961. font-size 10
  962. post-spacing 6 }}
  963. set items [get_filtered_configs extensions]
  964. set presentation [list]
  965. foreach i $items {
  966. lappend presentation [utils::get_extension_display_name_by_config_name $i]
  967. }
  968. set items_sorted [list]
  969. set presentation_sorted [list]
  970. foreach i [lsort -dictionary -indices $presentation] {
  971. lappend presentation_sorted [lindex $presentation $i]
  972. lappend items_sorted [lindex $items $i]
  973. }
  974. lappend menu_def presentation $presentation_sorted
  975. return [prepare_menu_list $items_sorted 10 $menu_def]
  976. }
  977. proc menu_add_extension_exec {item} {
  978. if {[catch {ext $item} errorText]} {
  979. osd::display_message $errorText error
  980. } else {
  981. menu_close_all
  982. }
  983. }
  984. proc menu_create_plugged_extensions_list {} {
  985. set menu_def {
  986. execute menu_remove_extension_exec
  987. font-size 8
  988. border-size 2
  989. width 200
  990. xpos 110
  991. ypos 130
  992. header { text "Select Extension to Remove"
  993. font-size 10
  994. post-spacing 6 }}
  995. set items [list_extensions]
  996. set possible_items [get_filtered_configs extensions]
  997. set useful_items [list]
  998. foreach item $items {
  999. if {$item in $possible_items} {
  1000. lappend useful_items $item
  1001. }
  1002. }
  1003. set presentation [list]
  1004. foreach i $useful_items {
  1005. lappend presentation [utils::get_extension_display_name_by_config_name ${i}]
  1006. }
  1007. lappend menu_def presentation $presentation
  1008. return [prepare_menu_list $useful_items 10 $menu_def]
  1009. }
  1010. proc menu_remove_extension_exec {item} {
  1011. menu_close_all
  1012. remove_extension $item
  1013. }
  1014. proc menu_create_connectors_list {} {
  1015. set menu_def {
  1016. execute menu_connector_exec
  1017. font-size 8
  1018. border-size 2
  1019. width 200
  1020. xpos 100
  1021. ypos 120
  1022. header { text "Connectors"
  1023. font-size 10
  1024. post-spacing 6 }}
  1025. set items [machine_info connector]
  1026. set presentation [list]
  1027. foreach item $items {
  1028. set plugged [get_pluggable_for_connector $item]
  1029. set plugged_presentation ""
  1030. if {$plugged ne ""} {
  1031. set plugged_presentation " ([machine_info pluggable $plugged])"
  1032. }
  1033. lappend presentation "[machine_info connector $item]: $plugged$plugged_presentation"
  1034. }
  1035. lappend menu_def presentation $presentation
  1036. return [prepare_menu_list $items 5 $menu_def]
  1037. }
  1038. proc menu_connector_exec {item} {
  1039. menu_create [create_menu_pluggable_list $item]
  1040. select_menu_item [get_pluggable_for_connector $item]
  1041. }
  1042. proc create_menu_pluggable_list {connector} {
  1043. set menu_def [list \
  1044. execute [list menu_plug_exec $connector] \
  1045. font-size 8 \
  1046. border-size 2 \
  1047. width 200 \
  1048. xpos 110 \
  1049. ypos 140 \
  1050. header [list text "What to Plug into [machine_info connector $connector]?" \
  1051. font-size 10 \
  1052. post-spacing 6 ]]
  1053. set items [list]
  1054. set class [machine_info connectionclass $connector]
  1055. # find out which pluggables are already plugged
  1056. # (currently a pluggable can be used only once per machine)
  1057. set already_plugged [list]
  1058. foreach other_connector [machine_info connector] {
  1059. set other_plugged [get_pluggable_for_connector $other_connector]
  1060. if {$other_plugged ne "" && $other_connector ne $connector} {
  1061. lappend already_plugged $other_plugged
  1062. }
  1063. }
  1064. # get a list of all pluggables that fit this connector
  1065. # and which are not plugged yet in other connectors
  1066. foreach pluggable [machine_info pluggable] {
  1067. if {$pluggable ni $already_plugged && [machine_info connectionclass $pluggable] eq $class} {
  1068. lappend items $pluggable
  1069. }
  1070. }
  1071. set presentation [list]
  1072. foreach item $items {
  1073. lappend presentation "$item: [machine_info pluggable $item]"
  1074. }
  1075. set plugged [get_pluggable_for_connector $connector]
  1076. if {$plugged ne ""} {
  1077. set items [linsert $items 0 "--unplug--"]
  1078. set presentation [linsert $presentation 0 "Nothing, unplug $plugged ([machine_info pluggable $plugged])"]
  1079. }
  1080. lappend menu_def presentation $presentation
  1081. return [prepare_menu_list $items 5 $menu_def]
  1082. }
  1083. proc menu_plug_exec {connector pluggable} {
  1084. set command ""
  1085. if {$pluggable eq "--unplug--"} {
  1086. set command "unplug {$connector}"
  1087. } else {
  1088. set command "plug {$connector} {$pluggable}"
  1089. }
  1090. #note: NO braces around $command
  1091. if {[catch $command errorText]} {
  1092. osd::display_message $errorText error
  1093. } else {
  1094. menu_close_top
  1095. # refresh the connectors menu
  1096. # The list must be recreated, so menu_refresh_top won't work
  1097. menu_close_top
  1098. menu_create [menu_create_connectors_list]
  1099. }
  1100. }
  1101. proc menu_create_toys_list {} {
  1102. set menu_def {
  1103. execute menu_toys_exec
  1104. font-size 8
  1105. border-size 2
  1106. width 200
  1107. xpos 100
  1108. ypos 120
  1109. header { text "Toys and Utilities"
  1110. font-size 10
  1111. post-spacing 6 }}
  1112. set items [list]
  1113. set presentation [list]
  1114. # This also picks up 'lazy' command names
  1115. foreach cmd [openmsx::all_command_names] {
  1116. if {[string match toggle_* $cmd]} {
  1117. lappend items $cmd
  1118. lappend presentation [string map {_ " "} [string range $cmd 7 end]]
  1119. }
  1120. }
  1121. lappend menu_def presentation $presentation
  1122. return [prepare_menu_list $items 5 $menu_def]
  1123. }
  1124. proc menu_toys_exec {toy} {
  1125. return [$toy]
  1126. }
  1127. proc ls {directory extensions} {
  1128. set dirs [list]
  1129. set specialdir [list]
  1130. set items [list]
  1131. if {[catch {
  1132. set files [glob -nocomplain -tails -directory $directory -types {f r} *]
  1133. set items [lsearch -regexp -all -inline -nocase $files .*\\.($extensions)]
  1134. set dirs [glob -nocomplain -tails -directory $directory -types {d r x} *]
  1135. set specialdir [glob -nocomplain -tails -directory $directory -types {hidden d} ".openMSX"]
  1136. } errorText]} {
  1137. osd::display_message "Unable to read dir $directory: $errorText" error
  1138. }
  1139. set dirs2 [list]
  1140. foreach dir [concat $dirs $specialdir] {
  1141. lappend dirs2 "$dir/"
  1142. }
  1143. set extra_entries [list]
  1144. set volumes [file volumes]
  1145. if {$directory ni $volumes} {
  1146. # check whether .. is readable (it's not always so on Android)
  1147. if {[file readable [file join $directory ..]]} {
  1148. lappend extra_entries ".."
  1149. }
  1150. } else {
  1151. if {[llength $volumes] > 1} {
  1152. set extra_entries $volumes
  1153. }
  1154. }
  1155. return [concat [lsort $extra_entries] [lsort $dirs2] [lsort $items]]
  1156. }
  1157. proc is_empty_dir {directory extensions} {
  1158. set files [list]
  1159. catch {set files [glob -nocomplain -tails -directory $directory -types {f r} *]}
  1160. set items [lsearch -regexp -all -inline -nocase $files .*\\.($extensions)]
  1161. if {[llength $items] != 0} {return false}
  1162. set dirs [list]
  1163. catch {set dirs [glob -nocomplain -tails -directory $directory -types {d r x} *]}
  1164. if {[llength $dirs] != 0} {return false}
  1165. set specialdir [list]
  1166. catch {set specialdir [glob -nocomplain -tails -directory $directory -types {hidden d} ".openMSX"]}
  1167. if {[llength $specialdir] != 0} {return false}
  1168. return true
  1169. }
  1170. proc menu_create_ROM_list {path slot} {
  1171. set menu_def [list execute [list menu_select_rom $slot] \
  1172. font-size 8 \
  1173. border-size 2 \
  1174. width 200 \
  1175. xpos 100 \
  1176. ypos 120 \
  1177. header { text "ROMs $::osd_rom_path" \
  1178. font-size 10 \
  1179. post-spacing 6 }]
  1180. set extensions "rom|ri|mx1|mx2|zip|gz"
  1181. set items [list]
  1182. set presentation [list]
  1183. if {[lindex [$slot] 2] ne "empty"} {
  1184. lappend items "--eject--"
  1185. lappend presentation "--eject-- [file tail [lindex [$slot] 1]]"
  1186. }
  1187. set i 1
  1188. foreach pool_path [filepool::get_paths_for_type rom] {
  1189. if {$path ne $pool_path && [file exists $pool_path] &&
  1190. ![is_empty_dir $pool_path $extensions]} {
  1191. lappend items $pool_path
  1192. lappend presentation "\[ROM Pool $i\]"
  1193. }
  1194. incr i
  1195. }
  1196. set files [ls $path $extensions]
  1197. set items [concat $items $files]
  1198. set presentation [concat $presentation $files]
  1199. lappend menu_def presentation $presentation
  1200. return [prepare_menu_list $items 10 $menu_def]
  1201. }
  1202. proc menu_select_rom {slot item} {
  1203. if {$item eq "--eject--"} {
  1204. menu_close_all
  1205. $slot eject
  1206. reset
  1207. } else {
  1208. set fullname [file join $::osd_rom_path $item]
  1209. if {[file isdirectory $fullname]} {
  1210. menu_close_top
  1211. set ::osd_rom_path [file normalize $fullname]
  1212. menu_create [menu_create_ROM_list $::osd_rom_path $slot]
  1213. } else {
  1214. set mappertype ""
  1215. set hash [sha1sum $fullname]
  1216. if {[catch {set mappertype [dict get [openmsx_info software $hash] mapper_type_name]}]} {
  1217. # not in the database, execute after selecting mapper type
  1218. menu_create [menu_create_mappertype_list $slot $fullname]
  1219. } else {
  1220. # in the database, so just execute
  1221. menu_rom_with_mappertype_exec $slot $fullname $mappertype
  1222. }
  1223. }
  1224. }
  1225. }
  1226. proc menu_rom_with_mappertype_exec {slot fullname mappertype} {
  1227. if {[catch {$slot $fullname -romtype $mappertype} errorText]} {
  1228. osd::display_message "Can't insert ROM: $errorText" error
  1229. } else {
  1230. menu_close_all
  1231. set rominfo [getlist_rom_info]
  1232. if {$rominfo eq ""} {
  1233. osd::display_message "No ROM information available..."
  1234. } else {
  1235. osd::display_message "Now running ROM:\nTitle:\nYear:\nCompany:\nCountry:\nStatus:\nRemark:"
  1236. append result " \n" \
  1237. "[dict get $rominfo title]\n" \
  1238. "[dict get $rominfo year]\n" \
  1239. "[dict get $rominfo company]\n" \
  1240. "[dict get $rominfo country]\n" \
  1241. "[dict get $rominfo status]\n"
  1242. if {[dict get $rominfo remark] ne ""} {
  1243. append result [dict get $rominfo remark]
  1244. } else {
  1245. append result "None"
  1246. }
  1247. set txt_size 6
  1248. set xpos 35
  1249. # TODO: prevent this from being duplicated from osd_widgets::text_box
  1250. if {$::scale_factor == 1} {
  1251. set txt_size 9
  1252. set xpos 53
  1253. }
  1254. # TODO: this code knows the internal name of the widget of osd::display_message proc... it shouldn't need to.
  1255. osd create text osd_display_message.rominfo_text -x $xpos -y 2 -size $txt_size -rgba 0xffffffff -text "$result"
  1256. }
  1257. reset
  1258. }
  1259. }
  1260. proc menu_create_mappertype_list {slot fullname} {
  1261. set menu_def [list execute [list menu_rom_with_mappertype_exec $slot $fullname] \
  1262. font-size 8 \
  1263. border-size 2 \
  1264. width 200 \
  1265. xpos 100 \
  1266. ypos 120 \
  1267. header { text "Select mapper type" \
  1268. font-size 10 \
  1269. post-spacing 6 }]
  1270. set items [openmsx_info romtype]
  1271. set presentation [list]
  1272. foreach i $items {
  1273. lappend presentation "[dict get [openmsx_info romtype $i] description]"
  1274. }
  1275. set items_sorted [list "auto"]
  1276. set presentation_sorted [list "Auto-detect (guess)"]
  1277. foreach i [lsort -dictionary -indices $presentation] {
  1278. lappend presentation_sorted [lindex $presentation $i]
  1279. lappend items_sorted [lindex $items $i]
  1280. }
  1281. lappend menu_def presentation $presentation_sorted
  1282. return [prepare_menu_list $items_sorted 10 $menu_def]
  1283. }
  1284. proc menu_create_disk_list {path drive} {
  1285. set menu_def [list execute [list menu_select_disk $drive] \
  1286. font-size 8 \
  1287. border-size 2 \
  1288. width 200 \
  1289. xpos 100 \
  1290. ypos 120 \
  1291. header { text "Disks $::osd_disk_path" \
  1292. font-size 10 \
  1293. post-spacing 6 }]
  1294. set cur_image [lindex [$drive] 1]
  1295. set extensions "dsk|zip|gz|xsa|dmk|di1|di2"
  1296. set items [list]
  1297. set presentation [list]
  1298. if {[lindex [$drive] 2] ne "empty readonly"} {
  1299. lappend items "--eject--"
  1300. lappend presentation "--eject-- [file tail $cur_image]"
  1301. }
  1302. set i 1
  1303. foreach pool_path [filepool::get_paths_for_type disk] {
  1304. if {$path ne $pool_path && [file exists $pool_path] &&
  1305. ![is_empty_dir $pool_path $extensions]} {
  1306. lappend items $pool_path
  1307. lappend presentation "\[Disk Pool $i\]"
  1308. }
  1309. incr i
  1310. }
  1311. if {$cur_image ne $path} {
  1312. lappend items "."
  1313. lappend presentation "--insert this dir as disk--"
  1314. }
  1315. set files [ls $path $extensions]
  1316. set items [concat $items $files]
  1317. set presentation [concat $presentation $files]
  1318. lappend menu_def presentation $presentation
  1319. return [prepare_menu_list $items 10 $menu_def]
  1320. }
  1321. proc menu_select_disk {drive item} {
  1322. if {$item eq "--eject--"} {
  1323. set cur_image [lindex [$drive] 1]
  1324. menu_close_all
  1325. $drive eject
  1326. osd::display_message "Disk $cur_image ejected!"
  1327. } else {
  1328. set fullname [file normalize [file join $::osd_disk_path $item]]
  1329. if {[file isdirectory $fullname] && $item ne "."} {
  1330. menu_close_top
  1331. set ::osd_disk_path [file normalize $fullname]
  1332. menu_create [menu_create_disk_list $::osd_disk_path $drive]
  1333. } else {
  1334. if {[catch {$drive $fullname} errorText]} {
  1335. osd::display_message "Can't insert disk: $errorText" error
  1336. } else {
  1337. menu_close_all
  1338. if {$item eq "."} { set item $fullname }
  1339. osd::display_message "Disk $item inserted!"
  1340. }
  1341. }
  1342. }
  1343. }
  1344. proc menu_create_tape_list {path} {
  1345. variable taperecordings_directory
  1346. set menu_def { execute menu_select_tape
  1347. font-size 8
  1348. border-size 2
  1349. width 200
  1350. xpos 100
  1351. ypos 120
  1352. header { text "Tapes $::osd_tape_path"
  1353. font-size 10
  1354. post-spacing 6 }}
  1355. set extensions "cas|wav|zip|gz"
  1356. set items [list]
  1357. set presentation [list]
  1358. lappend items "--create--"
  1359. lappend presentation "--create new and insert--"
  1360. set inserted [lindex [cassetteplayer] 1]
  1361. if {$inserted ne ""} {
  1362. lappend items "--eject--"
  1363. lappend presentation "--eject-- [file tail $inserted]"
  1364. lappend items "--rewind-"
  1365. lappend presentation "--rewind-- [file tail $inserted]"
  1366. }
  1367. if {$path ne $taperecordings_directory && [file exists $taperecordings_directory]} {
  1368. lappend items $taperecordings_directory
  1369. lappend presentation "\[My Tape Recordings\]"
  1370. }
  1371. set i 1
  1372. foreach pool_path [filepool::get_paths_for_type tape] {
  1373. if {$path ne $pool_path && [file exists $pool_path] &&
  1374. ![is_empty_dir $pool_path $extensions]} {
  1375. lappend items $pool_path
  1376. lappend presentation "\[Tape Pool $i\]"
  1377. }
  1378. incr i
  1379. }
  1380. set files [ls $path $extensions]
  1381. set items [concat $items $files]
  1382. set presentation [concat $presentation $files]
  1383. lappend menu_def presentation $presentation
  1384. return [prepare_menu_list $items 10 $menu_def]
  1385. }
  1386. proc menu_select_tape {item} {
  1387. variable taperecordings_directory
  1388. if {$item eq "--create--"} {
  1389. menu_close_all
  1390. osd::display_message [cassetteplayer new [menu_free_tape_name]]
  1391. } elseif {$item eq "--eject--"} {
  1392. menu_close_all
  1393. osd::display_message [cassetteplayer eject]
  1394. } elseif {$item eq "--rewind--"} {
  1395. menu_close_all
  1396. osd::display_message [cassetteplayer rewind]
  1397. } else {
  1398. set fullname [file join $::osd_tape_path $item]
  1399. if {[file isdirectory $fullname]} {
  1400. menu_close_top
  1401. set ::osd_tape_path [file normalize $fullname]
  1402. menu_create [menu_create_tape_list $::osd_tape_path]
  1403. } else {
  1404. if {[catch {cassetteplayer $fullname} errorText]} {
  1405. osd::display_message "Can't set tape: $errorText" error
  1406. } else {
  1407. osd::display_message "Inserted tape $item!"
  1408. menu_close_all
  1409. }
  1410. }
  1411. }
  1412. }
  1413. proc menu_free_tape_name {} {
  1414. variable taperecordings_directory
  1415. set existing [list]
  1416. foreach f [lsort [glob -tails -directory $taperecordings_directory -type f -nocomplain *.wav]] {
  1417. lappend existing [file rootname $f]
  1418. }
  1419. set i 1
  1420. while 1 {
  1421. set name [format "[guess_title untitled] %04d" $i]
  1422. if {$name ni $existing} {
  1423. return $name
  1424. }
  1425. incr i
  1426. }
  1427. }
  1428. proc menu_create_hdd_list {path drive} {
  1429. return [prepare_menu_list [ls $path "dsk|zip|gz|hdd"] \
  1430. 10 \
  1431. [list execute [list menu_select_hdd $drive]\
  1432. font-size 8 \
  1433. border-size 2 \
  1434. width 200 \
  1435. xpos 100 \
  1436. ypos 120 \
  1437. header { text "Hard disk images $::osd_hdd_path"
  1438. font-size 10
  1439. post-spacing 6 }]]
  1440. }
  1441. proc menu_select_hdd {drive item} {
  1442. set fullname [file join $::osd_hdd_path $item]
  1443. if {[file isdirectory $fullname]} {
  1444. menu_close_top
  1445. set ::osd_hdd_path [file normalize $fullname]
  1446. menu_create [menu_create_hdd_list $::osd_hdd_path $drive]
  1447. } else {
  1448. confirm_action "Really power off to change HDD image?" osd_menu::confirm_change_hdd [list $item $drive]
  1449. }
  1450. }
  1451. proc confirm_change_hdd {item result} {
  1452. menu_close_top
  1453. if {$result eq "Yes"} {
  1454. set fullname [file join $::osd_hdd_path [lindex $item 0]]
  1455. if {[catch {set ::power off; [lindex $item 1] $fullname} errorText]} {
  1456. osd::display_message "Can't change hard disk image: $errorText" error
  1457. # TODO: we already powered off even though the file may be invalid... save state first?
  1458. } else {
  1459. osd::display_message "Changed hard disk image to [lindex $item 0]!"
  1460. menu_close_all
  1461. }
  1462. set ::power on
  1463. }
  1464. }
  1465. proc menu_create_ld_list {path} {
  1466. set eject_item [list]
  1467. set inserted [lindex [laserdiscplayer] 1]
  1468. if {$inserted ne ""} {
  1469. lappend eject_item "--eject-- [file tail $inserted]"
  1470. }
  1471. return [prepare_menu_list [concat $eject_item [ls $path "ogv"]] \
  1472. 10 \
  1473. { execute menu_select_ld
  1474. font-size 8
  1475. border-size 2
  1476. width 200
  1477. xpos 100
  1478. ypos 120
  1479. header { text "Laserdiscs $::osd_ld_path"
  1480. font-size 10
  1481. post-spacing 6 }}]
  1482. }
  1483. proc menu_select_ld {item} {
  1484. if {[string range $item 0 8] eq "--eject--"} {
  1485. menu_close_all
  1486. osd::display_message [laserdiscplayer eject]
  1487. } else {
  1488. set fullname [file join $::osd_ld_path $item]
  1489. if {[file isdirectory $fullname]} {
  1490. menu_close_top
  1491. set ::osd_ld_path [file normalize $fullname]
  1492. menu_create [menu_create_ld_list $::osd_ld_path]
  1493. } else {
  1494. if {[catch {laserdiscplayer insert $fullname} errorText]} {
  1495. osd::display_message "Can't load LaserDisc: $errorText" error
  1496. } else {
  1497. osd::display_message "Loaded LaserDisc $item!"
  1498. menu_close_all
  1499. }
  1500. }
  1501. }
  1502. }
  1503. proc get_savestates_list_presentation_sorted {} {
  1504. set presentation [list]
  1505. foreach i [lsort -integer -index 1 -decreasing [savestate::list_savestates_raw]] {
  1506. if {[info commands clock] ne ""} {
  1507. set pres_str [format "%s (%s)" [lindex $i 0] [clock format [lindex $i 1] -format "%x - %X"]]
  1508. } else {
  1509. set pres_str [lindex $i 0]
  1510. }
  1511. lappend presentation $pres_str
  1512. }
  1513. return $presentation
  1514. }
  1515. proc menu_create_load_state {} {
  1516. set menu_def \
  1517. { execute menu_loadstate_exec
  1518. font-size 8
  1519. border-size 2
  1520. width 200
  1521. xpos 100
  1522. ypos 120
  1523. on-open {osd create rectangle "preview" -x 225 -y 5 -w 90 -h 70 -rgba 0x30303080 -scaled true}
  1524. on-close {osd destroy "preview"}
  1525. on-select menu_loadstate_select
  1526. on-deselect menu_loadstate_deselect
  1527. header { text "Load State"
  1528. font-size 10
  1529. post-spacing 6 }}
  1530. set items [list_savestates -t]
  1531. lappend menu_def presentation [get_savestates_list_presentation_sorted]
  1532. return [prepare_menu_list $items 10 $menu_def]
  1533. }
  1534. proc menu_create_save_state {} {
  1535. set items [concat [list "create new"] [list_savestates -t]]
  1536. set menu_def \
  1537. { execute menu_savestate_exec
  1538. font-size 8
  1539. border-size 2
  1540. width 200
  1541. xpos 100
  1542. ypos 120
  1543. on-open {osd create rectangle "preview" -x 225 -y 5 -w 90 -h 70 -rgba 0x30303080 -scaled true}
  1544. on-close {osd destroy "preview"}
  1545. on-select menu_loadstate_select
  1546. on-deselect menu_loadstate_deselect
  1547. header { text "Save State"
  1548. font-size 10
  1549. post-spacing 6 }}
  1550. lappend menu_def presentation [concat [list "create new"] [get_savestates_list_presentation_sorted]]
  1551. return [prepare_menu_list $items 10 $menu_def]
  1552. }
  1553. proc menu_loadstate_select {item} {
  1554. set png $::env(OPENMSX_USER_DATA)/../savestates/${item}.png
  1555. catch {osd create rectangle "preview.image" -relx 0.05 -rely 0.05 -w 80 -h 60 -image $png}
  1556. }
  1557. proc menu_loadstate_deselect {item} {
  1558. osd destroy "preview.image"
  1559. }
  1560. proc menu_loadstate_exec {item} {
  1561. if {[catch {loadstate $item} errorText]} {
  1562. osd::display_message $errorText error
  1563. } else {
  1564. menu_close_all
  1565. }
  1566. }
  1567. proc menu_savestate_exec {item} {
  1568. if {$item eq "create new"} {
  1569. set item [menu_free_savestate_name]
  1570. confirm_save_state $item "Yes"
  1571. menu_close_all
  1572. } else {
  1573. confirm_action "Overwrite $item?" osd_menu::confirm_save_state $item
  1574. }
  1575. }
  1576. proc confirm_save_state {item result} {
  1577. menu_close_top
  1578. if {$result eq "Yes"} {
  1579. if {[catch {savestate $item} errorText]} {
  1580. osd::display_message $errorText error
  1581. } else {
  1582. osd::display_message "State saved to $item!"
  1583. menu_close_all
  1584. }
  1585. }
  1586. }
  1587. proc menu_free_savestate_name {} {
  1588. set existing [list_savestates]
  1589. set i 1
  1590. while 1 {
  1591. set name [format "[guess_title savestate] %04d" $i]
  1592. if {$name ni $existing} {
  1593. return $name
  1594. }
  1595. incr i
  1596. }
  1597. }
  1598. proc confirm_action {text action item} {
  1599. set items [list "No" "Yes"]
  1600. set menu_def [list execute [list $action $item] \
  1601. font-size 8 \
  1602. border-size 2 \
  1603. width 210 \
  1604. xpos 100 \
  1605. ypos 100 \
  1606. header [list text $text \
  1607. font-size 10 \
  1608. post-spacing 6 ]]
  1609. osd_menu::menu_create [osd_menu::prepare_menu_list $items [llength $items] $menu_def]
  1610. }
  1611. # Keep openmsx console from interfering with the osd menu:
  1612. # when the console is activated while the osd menu is already open, we want
  1613. # to prevent the osd menu from receiving the keys that are pressed in the
  1614. # console.
  1615. variable old_console $::console
  1616. proc console_input_layer {name1 name1 op} {
  1617. global console
  1618. variable old_console
  1619. if {$console == $old_console} return
  1620. set old_console $console
  1621. if {$console} {
  1622. activate_input_layer console -blocking
  1623. } else {
  1624. deactivate_input_layer console
  1625. }
  1626. }
  1627. trace add variable ::console write [namespace code console_input_layer]
  1628. namespace export main_menu_open
  1629. namespace export main_menu_close
  1630. namespace export main_menu_toggle
  1631. } ;# namespace osd_menu
  1632. namespace import osd_menu::*