page.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2019 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2019 Tobias Geerinckx-Rice <me@tobias.gr>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (gnu installer newt page)
  21. #:use-module (gnu installer utils)
  22. #:use-module (gnu installer newt utils)
  23. #:use-module (guix i18n)
  24. #:use-module (ice-9 i18n)
  25. #:use-module (ice-9 match)
  26. #:use-module (ice-9 receive)
  27. #:use-module (srfi srfi-1)
  28. #:use-module (srfi srfi-26)
  29. #:use-module (newt)
  30. #:export (draw-info-page
  31. draw-connecting-page
  32. run-input-page
  33. run-error-page
  34. run-confirmation-page
  35. run-listbox-selection-page
  36. run-scale-page
  37. run-checkbox-tree-page
  38. run-file-textbox-page))
  39. ;;; Commentary:
  40. ;;;
  41. ;;; Some helpers around guile-newt to draw or run generic pages. The
  42. ;;; difference between 'draw' and 'run' terms comes from newt library. A page
  43. ;;; is drawn when the form it contains does not expect any user
  44. ;;; interaction. In that case, it is necessary to call (newt-refresh) to force
  45. ;;; the page to be displayed. When a form is 'run', it is blocked waiting for
  46. ;;; any action from the user (press a button, input some text, ...).
  47. ;;;
  48. ;;; Code:
  49. (define (draw-info-page text title)
  50. "Draw an informative page with the given TEXT as content. Set the title of
  51. this page to TITLE."
  52. (let* ((text-box
  53. (make-reflowed-textbox -1 -1 text 40
  54. #:flags FLAG-BORDER))
  55. (grid (make-grid 1 1))
  56. (form (make-form)))
  57. (set-grid-field grid 0 0 GRID-ELEMENT-COMPONENT text-box)
  58. (add-component-to-form form text-box)
  59. (make-wrapped-grid-window grid title)
  60. (draw-form form)
  61. ;; This call is imperative, otherwise the form won't be displayed. See the
  62. ;; explanation in the above commentary.
  63. (newt-refresh)
  64. form))
  65. (define (draw-connecting-page service-name)
  66. "Draw a page to indicate a connection in in progress."
  67. (draw-info-page
  68. (format #f (G_ "Connecting to ~a, please wait.") service-name)
  69. (G_ "Connection in progress")))
  70. (define* (run-input-page text title
  71. #:key
  72. (allow-empty-input? #f)
  73. (default-text #f)
  74. (input-visibility-checkbox? #f)
  75. (input-field-width 40)
  76. (input-flags 0))
  77. "Run a page to prompt user for an input. The given TEXT will be displayed
  78. above the input field. The page title is set to TITLE. Unless
  79. allow-empty-input? is set to #t, an error page will be displayed if the user
  80. enters an empty input. INPUT-FLAGS is a bitwise-or'd set of flags for the
  81. input box, such as FLAG-PASSWORD."
  82. (let* ((text-box
  83. (make-reflowed-textbox -1 -1 text
  84. input-field-width
  85. #:flags FLAG-BORDER))
  86. (input-visible-cb
  87. (make-checkbox -1 -1 (G_ "Show") #\space "x "))
  88. (input-flags* (if input-visibility-checkbox?
  89. (logior FLAG-PASSWORD FLAG-SCROLL
  90. input-flags)
  91. input-flags))
  92. (input-entry (make-entry -1 -1 20
  93. #:flags input-flags*))
  94. (ok-button (make-button -1 -1 (G_ "OK")))
  95. (grid (vertically-stacked-grid
  96. GRID-ELEMENT-COMPONENT text-box
  97. GRID-ELEMENT-SUBGRID
  98. (apply
  99. horizontal-stacked-grid
  100. GRID-ELEMENT-COMPONENT input-entry
  101. `(,@(if input-visibility-checkbox?
  102. (list GRID-ELEMENT-COMPONENT input-visible-cb)
  103. '())))
  104. GRID-ELEMENT-COMPONENT ok-button))
  105. (form (make-form)))
  106. (add-component-callback
  107. input-visible-cb
  108. (lambda (component)
  109. (set-entry-flags input-entry
  110. FLAG-PASSWORD
  111. FLAG-ROLE-TOGGLE)))
  112. (when default-text
  113. (set-entry-text input-entry default-text))
  114. (add-form-to-grid grid form #t)
  115. (make-wrapped-grid-window grid title)
  116. (let ((error-page (lambda ()
  117. (run-error-page (G_ "Please enter a non empty input.")
  118. (G_ "Empty input")))))
  119. (let loop ()
  120. (receive (exit-reason argument)
  121. (run-form form)
  122. (let ((input (entry-value input-entry)))
  123. (if (and (not allow-empty-input?)
  124. (eq? exit-reason 'exit-component)
  125. (string=? input ""))
  126. (begin
  127. ;; Display the error page.
  128. (error-page)
  129. ;; Set the focus back to the input input field.
  130. (set-current-component form input-entry)
  131. (loop))
  132. (begin
  133. (destroy-form-and-pop form)
  134. input))))))))
  135. (define (run-error-page text title)
  136. "Run a page to inform the user of an error. The page contains the given TEXT
  137. to explain the error and an \"OK\" button to acknowledge the error. The title
  138. of the page is set to TITLE."
  139. (let* ((text-box
  140. (make-reflowed-textbox -1 -1 text 40
  141. #:flags FLAG-BORDER))
  142. (grid (make-grid 1 2))
  143. (ok-button (make-button -1 -1 "OK"))
  144. (form (make-form)))
  145. (set-grid-field grid 0 0 GRID-ELEMENT-COMPONENT text-box)
  146. (set-grid-field grid 0 1 GRID-ELEMENT-COMPONENT ok-button
  147. #:pad-top 1)
  148. ;; Set the background color to red to indicate something went wrong.
  149. (newt-set-color COLORSET-ROOT "white" "red")
  150. (add-components-to-form form text-box ok-button)
  151. (make-wrapped-grid-window grid title)
  152. (run-form form)
  153. ;; Restore the background to its original color.
  154. (newt-set-color COLORSET-ROOT "white" "blue")
  155. (destroy-form-and-pop form)))
  156. (define* (run-confirmation-page text title
  157. #:key (exit-button-procedure (const #f)))
  158. "Run a page to inform the user of an error. The page contains the given TEXT
  159. to explain the error and an \"OK\" button to acknowledge the error. The title
  160. of the page is set to TITLE."
  161. (let* ((text-box
  162. (make-reflowed-textbox -1 -1 text 40
  163. #:flags FLAG-BORDER))
  164. (ok-button (make-button -1 -1 (G_ "Continue")))
  165. (exit-button (make-button -1 -1 (G_ "Exit")))
  166. (grid (vertically-stacked-grid
  167. GRID-ELEMENT-COMPONENT text-box
  168. GRID-ELEMENT-SUBGRID
  169. (horizontal-stacked-grid
  170. GRID-ELEMENT-COMPONENT ok-button
  171. GRID-ELEMENT-COMPONENT exit-button)))
  172. (form (make-form)))
  173. (add-form-to-grid grid form #t)
  174. (make-wrapped-grid-window grid title)
  175. (receive (exit-reason argument)
  176. (run-form form)
  177. (dynamic-wind
  178. (const #t)
  179. (lambda ()
  180. (case exit-reason
  181. ((exit-component)
  182. (cond
  183. ((components=? argument ok-button)
  184. #t)
  185. ((components=? argument exit-button)
  186. (exit-button-procedure))))))
  187. (lambda ()
  188. (destroy-form-and-pop form))))))
  189. (define* (run-listbox-selection-page #:key
  190. info-text
  191. title
  192. (info-textbox-width 50)
  193. listbox-items
  194. listbox-item->text
  195. (listbox-height 20)
  196. (listbox-default-item #f)
  197. (listbox-allow-multiple? #f)
  198. (sort-listbox-items? #t)
  199. (allow-delete? #f)
  200. (skip-item-procedure?
  201. (const #f))
  202. button-text
  203. (button-callback-procedure
  204. (const #t))
  205. (button2-text #f)
  206. (button2-callback-procedure
  207. (const #t))
  208. (listbox-callback-procedure
  209. identity)
  210. (hotkey-callback-procedure
  211. (const #t)))
  212. "Run a page asking the user to select an item in a listbox. The page
  213. contains, stacked vertically from the top to the bottom, an informative text
  214. set to INFO-TEXT, a listbox and a button. The listbox will be filled with
  215. LISTBOX-ITEMS converted to text by applying the procedure LISTBOX-ITEM->TEXT
  216. on every item. The selected item from LISTBOX-ITEMS is returned. The button
  217. text is set to BUTTON-TEXT and the procedure BUTTON-CALLBACK-PROCEDURE called
  218. when it is pressed. The procedure LISTBOX-CALLBACK-PROCEDURE is called when an
  219. item from the listbox is selected (by pressing the <ENTER> key).
  220. INFO-TEXTBOX-WIDTH is the width of the textbox where INFO-TEXT will be
  221. displayed. LISTBOX-HEIGHT is the height of the listbox.
  222. If LISTBOX-DEFAULT-ITEM is set to the value of one of the items in
  223. LISTBOX-ITEMS, it will be selected by default. Otherwise, the first element of
  224. the listbox is selected.
  225. If LISTBOX-ALLOW-MULTIPLE? is set to #t, multiple items from the listbox can
  226. be selected (using the <SPACE> key). It that case, a list containing the
  227. selected items will be returned.
  228. If SORT-LISTBOX-ITEMS? is set to #t, the listbox items are sorted using
  229. 'string-locale<?' procedure (after being converted to text).
  230. If ALLOW-DELETE? is #t, the form will return if the <DELETE> key is pressed,
  231. otherwise nothing will happen.
  232. Each time the listbox current item changes, call SKIP-ITEM-PROCEDURE? with the
  233. current listbox item as argument. If it returns #t, skip the element and jump
  234. to the next/previous one depending on the previous item, otherwise do
  235. nothing."
  236. (define (fill-listbox listbox items)
  237. "Append the given ITEMS to LISTBOX, once they have been converted to text
  238. with LISTBOX-ITEM->TEXT. Each item appended to the LISTBOX is given a key by
  239. newt. Save this key by returning an association list under the form:
  240. ((NEWT-LISTBOX-KEY . ITEM) ...)
  241. where NEWT-LISTBOX-KEY is the key returned by APPEND-ENTRY-TO-LISTBOX, when
  242. ITEM was inserted into LISTBOX."
  243. (map (lambda (item)
  244. (let* ((text (listbox-item->text item))
  245. (key (append-entry-to-listbox listbox text)))
  246. (cons key item)))
  247. items))
  248. (define (sort-listbox-items listbox-items)
  249. "Return LISTBOX-ITEMS sorted using the 'string-locale<?' procedure on the text
  250. corresponding to each item in the list."
  251. (let* ((items (map (lambda (item)
  252. (cons item (listbox-item->text item)))
  253. listbox-items))
  254. (sorted-items
  255. (sort items (lambda (a b)
  256. (let ((text-a (cdr a))
  257. (text-b (cdr b)))
  258. (string-locale<? text-a text-b))))))
  259. (map car sorted-items)))
  260. ;; Store the last selected listbox item's key.
  261. (define last-listbox-key (make-parameter #f))
  262. (define (previous-key keys key)
  263. (let ((index (list-index (cut eq? key <>) keys)))
  264. (and index
  265. (> index 0)
  266. (list-ref keys (- index 1)))))
  267. (define (next-key keys key)
  268. (let ((index (list-index (cut eq? key <>) keys)))
  269. (and index
  270. (< index (- (length keys) 1))
  271. (list-ref keys (+ index 1)))))
  272. (define (set-default-item listbox listbox-keys default-item)
  273. "Set the default item of LISTBOX to DEFAULT-ITEM. LISTBOX-KEYS is the
  274. association list returned by the FILL-LISTBOX procedure. It is used because
  275. the current listbox item has to be selected by key."
  276. (for-each (match-lambda
  277. ((key . item)
  278. (when (equal? item default-item)
  279. (set-current-listbox-entry-by-key listbox key))))
  280. listbox-keys))
  281. (let* ((listbox (make-listbox
  282. -1 -1
  283. listbox-height
  284. (logior FLAG-SCROLL FLAG-BORDER FLAG-RETURNEXIT
  285. (if listbox-allow-multiple?
  286. FLAG-MULTIPLE
  287. 0))))
  288. (form (make-form))
  289. (info-textbox
  290. (make-reflowed-textbox -1 -1 info-text
  291. info-textbox-width
  292. #:flags FLAG-BORDER))
  293. (button (make-button -1 -1 button-text))
  294. (button2 (and button2-text
  295. (make-button -1 -1 button2-text)))
  296. (grid (vertically-stacked-grid
  297. GRID-ELEMENT-COMPONENT info-textbox
  298. GRID-ELEMENT-COMPONENT listbox
  299. GRID-ELEMENT-SUBGRID
  300. (apply
  301. horizontal-stacked-grid
  302. GRID-ELEMENT-COMPONENT button
  303. `(,@(if button2
  304. (list GRID-ELEMENT-COMPONENT button2)
  305. '())))))
  306. (sorted-items (if sort-listbox-items?
  307. (sort-listbox-items listbox-items)
  308. listbox-items))
  309. (keys (fill-listbox listbox sorted-items)))
  310. ;; On every listbox element change, check if we need to skip it. If yes,
  311. ;; depending on the 'last-listbox-key', jump forward or backward. If no,
  312. ;; do nothing.
  313. (add-component-callback
  314. listbox
  315. (lambda (component)
  316. (let* ((current-key (current-listbox-entry listbox))
  317. (listbox-keys (map car keys))
  318. (last-key (last-listbox-key))
  319. (item (assoc-ref keys current-key))
  320. (prev-key (previous-key listbox-keys current-key))
  321. (next-key (next-key listbox-keys current-key)))
  322. ;; Update last-listbox-key before a potential call to
  323. ;; set-current-listbox-entry-by-key, because it will immediately
  324. ;; cause this callback to be called for the new entry.
  325. (last-listbox-key current-key)
  326. (when (skip-item-procedure? item)
  327. (when (eq? prev-key last-key)
  328. (if next-key
  329. (set-current-listbox-entry-by-key listbox next-key)
  330. (set-current-listbox-entry-by-key listbox prev-key)))
  331. (when (eq? next-key last-key)
  332. (if prev-key
  333. (set-current-listbox-entry-by-key listbox prev-key)
  334. (set-current-listbox-entry-by-key listbox next-key)))))))
  335. (when listbox-default-item
  336. (set-default-item listbox keys listbox-default-item))
  337. (when allow-delete?
  338. (form-add-hotkey form KEY-DELETE))
  339. (add-form-to-grid grid form #t)
  340. (make-wrapped-grid-window grid title)
  341. (receive (exit-reason argument)
  342. (run-form form)
  343. (dynamic-wind
  344. (const #t)
  345. (lambda ()
  346. (case exit-reason
  347. ((exit-component)
  348. (cond
  349. ((components=? argument button)
  350. (button-callback-procedure))
  351. ((and button2
  352. (components=? argument button2))
  353. (button2-callback-procedure))
  354. ((components=? argument listbox)
  355. (if listbox-allow-multiple?
  356. (let* ((entries (listbox-selection listbox))
  357. (items (map (lambda (entry)
  358. (assoc-ref keys entry))
  359. entries)))
  360. (listbox-callback-procedure items))
  361. (let* ((entry (current-listbox-entry listbox))
  362. (item (assoc-ref keys entry)))
  363. (listbox-callback-procedure item))))))
  364. ((exit-hotkey)
  365. (let* ((entry (current-listbox-entry listbox))
  366. (item (assoc-ref keys entry)))
  367. (hotkey-callback-procedure argument item)))))
  368. (lambda ()
  369. (destroy-form-and-pop form))))))
  370. (define* (run-scale-page #:key
  371. title
  372. info-text
  373. (info-textbox-width 50)
  374. (scale-width 40)
  375. (scale-full-value 100)
  376. scale-update-proc
  377. (max-scale-update 5))
  378. "Run a page with a progress bar (called 'scale' in newt). The given
  379. INFO-TEXT is displayed in a textbox above the scale. The width of the textbox
  380. is set to INFO-TEXTBOX-WIDTH. The width of the scale is set to
  381. SCALE-WIDTH. SCALE-FULL-VALUE indicates the value that correspond to 100% of
  382. the scale.
  383. The procedure SCALE-UPDATE-PROC shall return a new scale
  384. value. SCALE-UPDATE-PROC will be called until the returned value is superior
  385. or equal to SCALE-FULL-VALUE, but no more than MAX-SCALE-UPDATE times. An
  386. error is raised if the MAX-SCALE-UPDATE limit is reached."
  387. (let* ((info-textbox
  388. (make-reflowed-textbox -1 -1 info-text
  389. info-textbox-width
  390. #:flags FLAG-BORDER))
  391. (scale (make-scale -1 -1 scale-width scale-full-value))
  392. (grid (vertically-stacked-grid
  393. GRID-ELEMENT-COMPONENT info-textbox
  394. GRID-ELEMENT-COMPONENT scale))
  395. (form (make-form)))
  396. (add-form-to-grid grid form #t)
  397. (make-wrapped-grid-window grid title)
  398. (draw-form form)
  399. ;; This call is imperative, otherwise the form won't be displayed. See the
  400. ;; explanation in the above commentary.
  401. (newt-refresh)
  402. (dynamic-wind
  403. (const #t)
  404. (lambda ()
  405. (let loop ((i max-scale-update)
  406. (last-value 0))
  407. (let ((value (scale-update-proc last-value)))
  408. (set-scale-value scale value)
  409. ;; Same as above.
  410. (newt-refresh)
  411. (unless (>= value scale-full-value)
  412. (if (> i 0)
  413. (loop (- i 1) value)
  414. (error "Max scale updates reached."))))))
  415. (lambda ()
  416. (destroy-form-and-pop form)))))
  417. (define %none-selected
  418. (circular-list #f))
  419. (define* (run-checkbox-tree-page #:key
  420. info-text
  421. title
  422. items
  423. (selection %none-selected)
  424. item->text
  425. (info-textbox-width 50)
  426. (checkbox-tree-height 10)
  427. (ok-button-callback-procedure
  428. (const #t))
  429. (exit-button-callback-procedure
  430. (const #t)))
  431. "Run a page allowing the user to select one or multiple items among ITEMS in
  432. a checkbox list. The page contains vertically stacked from the top to the
  433. bottom, an informative text set to INFO-TEXT, the checkbox list and two
  434. buttons, 'Ok' and 'Exit'. The page title's is set to TITLE. ITEMS are
  435. converted to text using ITEM->TEXT before being displayed in the checkbox
  436. list. SELECTION is a list of Booleans of the same length as ITEMS that
  437. specifies which items are initially checked.
  438. INFO-TEXTBOX-WIDTH is the width of the textbox where INFO-TEXT will be
  439. displayed. CHECKBOX-TREE-HEIGHT is the height of the checkbox list.
  440. OK-BUTTON-CALLBACK-PROCEDURE is called when the 'Ok' button is pressed.
  441. EXIT-BUTTON-CALLBACK-PROCEDURE is called when the 'Exit' button is
  442. pressed.
  443. This procedure returns the list of checked items in the checkbox list among
  444. ITEMS when 'Ok' is pressed."
  445. (define (fill-checkbox-tree checkbox-tree items)
  446. (map (lambda (item selected?)
  447. (let* ((item-text (item->text item))
  448. (key (add-entry-to-checkboxtree checkbox-tree item-text
  449. (if selected?
  450. FLAG-SELECTED
  451. 0))))
  452. (cons key item)))
  453. items
  454. selection))
  455. (let* ((checkbox-tree
  456. (make-checkboxtree -1 -1
  457. checkbox-tree-height
  458. FLAG-BORDER))
  459. (info-textbox
  460. (make-reflowed-textbox -1 -1 info-text
  461. info-textbox-width
  462. #:flags FLAG-BORDER))
  463. (ok-button (make-button -1 -1 (G_ "OK")))
  464. (exit-button (make-button -1 -1 (G_ "Exit")))
  465. (grid (vertically-stacked-grid
  466. GRID-ELEMENT-COMPONENT info-textbox
  467. GRID-ELEMENT-COMPONENT checkbox-tree
  468. GRID-ELEMENT-SUBGRID
  469. (horizontal-stacked-grid
  470. GRID-ELEMENT-COMPONENT ok-button
  471. GRID-ELEMENT-COMPONENT exit-button)))
  472. (keys (fill-checkbox-tree checkbox-tree items))
  473. (form (make-form)))
  474. (add-form-to-grid grid form #t)
  475. (make-wrapped-grid-window grid title)
  476. (receive (exit-reason argument)
  477. (run-form form)
  478. (dynamic-wind
  479. (const #t)
  480. (lambda ()
  481. (case exit-reason
  482. ((exit-component)
  483. (cond
  484. ((components=? argument ok-button)
  485. (let* ((entries (current-checkbox-selection checkbox-tree))
  486. (current-items (map (lambda (entry)
  487. (assoc-ref keys entry))
  488. entries)))
  489. (ok-button-callback-procedure)
  490. current-items))
  491. ((components=? argument exit-button)
  492. (exit-button-callback-procedure))))))
  493. (lambda ()
  494. (destroy-form-and-pop form))))))
  495. (define* (run-file-textbox-page #:key
  496. info-text
  497. title
  498. file
  499. (info-textbox-width 50)
  500. (file-textbox-width 50)
  501. (file-textbox-height 30)
  502. (exit-button? #t)
  503. (ok-button-callback-procedure
  504. (const #t))
  505. (exit-button-callback-procedure
  506. (const #t)))
  507. (let* ((info-textbox
  508. (make-reflowed-textbox -1 -1 info-text
  509. info-textbox-width
  510. #:flags FLAG-BORDER))
  511. (file-text (read-all file))
  512. (file-textbox
  513. (make-textbox -1 -1
  514. file-textbox-width
  515. file-textbox-height
  516. (logior FLAG-SCROLL FLAG-BORDER)))
  517. (ok-button (make-button -1 -1 (G_ "OK")))
  518. (exit-button (make-button -1 -1 (G_ "Exit")))
  519. (grid (vertically-stacked-grid
  520. GRID-ELEMENT-COMPONENT info-textbox
  521. GRID-ELEMENT-COMPONENT file-textbox
  522. GRID-ELEMENT-SUBGRID
  523. (apply
  524. horizontal-stacked-grid
  525. GRID-ELEMENT-COMPONENT ok-button
  526. `(,@(if exit-button?
  527. (list GRID-ELEMENT-COMPONENT exit-button)
  528. '())))))
  529. (form (make-form)))
  530. (set-textbox-text file-textbox
  531. (receive (_w _h text)
  532. (reflow-text file-text
  533. file-textbox-width
  534. 0 0)
  535. text))
  536. (add-form-to-grid grid form #t)
  537. (make-wrapped-grid-window grid title)
  538. (receive (exit-reason argument)
  539. (run-form form)
  540. (dynamic-wind
  541. (const #t)
  542. (lambda ()
  543. (case exit-reason
  544. ((exit-component)
  545. (cond
  546. ((components=? argument ok-button)
  547. (ok-button-callback-procedure))
  548. ((and exit-button?
  549. (components=? argument exit-button))
  550. (exit-button-callback-procedure))))))
  551. (lambda ()
  552. (destroy-form-and-pop form))))))