Ticket #1530
[pantumic.git] / src / help.c
blob538f894ee5e1d4b02c8cd5d1ba43cc4020658e4f
1 /* Hypertext file browser.
2 Copyright (C) 1994, 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3 2005, 2006, 2007 Free Software Foundation, Inc.
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 /** \file help.c
22 * \brief Source: hypertext file browser
24 * Implements the hypertext file viewer.
25 * The hypertext file is a file that may have one or more nodes. Each
26 * node ends with a ^D character and starts with a bracket, then the
27 * name of the node and then a closing bracket. Right after the closing
28 * bracket a newline is placed. This newline is not to be displayed by
29 * the help viewer and must be skipped - its sole purpose is to faciliate
30 * the work of the people managing the help file template (xnc.hlp) .
32 * Links in the hypertext file are specified like this: the text that
33 * will be highlighted should have a leading ^A, then it comes the
34 * text, then a ^B indicating that highlighting is done, then the name
35 * of the node you want to link to and then a ^C.
37 * The file must contain a ^D at the beginning and at the end of the
38 * file or the program will not be able to detect the end of file.
40 * Lazyness/widgeting attack: This file does use the dialog manager
41 * and uses mainly the dialog to achieve the help work. there is only
42 * one specialized widget and it's only used to forward the mouse messages
43 * to the appropiate routine.
47 #include <config.h>
49 #include <errno.h>
50 #include <stdio.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
54 #include "global.h"
56 #include "../src/tty/tty.h"
57 #include "../src/tty/color.h"
58 #include "../src/tty/mouse.h"
59 #include "../src/tty/key.h"
61 #include "help.h"
62 #include "dialog.h" /* For Dlg_head */
63 #include "widget.h" /* For Widget */
64 #include "wtools.h" /* For common_dialog_repaint() */
65 #include "strutil.h"
67 #define MAXLINKNAME 80
68 #define HISTORY_SIZE 20
69 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
71 #define STRING_LINK_START "\01"
72 #define STRING_LINK_POINTER "\02"
73 #define STRING_LINK_END "\03"
74 #define STRING_NODE_END "\04"
76 static char *data = NULL; /* Pointer to the loaded data file */
77 static int help_lines; /* Lines in help viewer */
78 static int history_ptr; /* For the history queue */
79 static const char *main_node; /* The main node */
80 static const char *last_shown = NULL; /* Last byte shown in a screen */
81 static int end_of_node = 0; /* Flag: the last character of the node shown? */
82 static const char *currentpoint;
83 static const char *selected_item;
85 /* The widget variables */
86 static Dlg_head *whelp;
88 static struct {
89 const char *page; /* Pointer to the selected page */
90 const char *link; /* Pointer to the selected link */
91 } history [HISTORY_SIZE];
93 /* Link areas for the mouse */
94 typedef struct Link_Area {
95 int x1, y1, x2, y2;
96 const char *link_name;
97 struct Link_Area *next;
98 } Link_Area;
100 static Link_Area *link_area = NULL;
101 static int inside_link_area = 0;
103 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
105 /* returns the position where text was found in the start buffer */
106 /* or 0 if not found */
107 static const char *
108 search_string (const char *start, const char *text)
110 const char *result = NULL;
111 char *local_text = g_strdup (text);
112 char *d = local_text;
113 const char *e = start;
115 /* fmt sometimes replaces a space with a newline in the help file */
116 /* Replace the newlines in the link name with spaces to correct the situation */
117 while (*d){
118 if (*d == '\n')
119 *d = ' ';
120 str_next_char (&d);
122 /* Do search */
123 for (d = local_text; *e; e++){
124 if (*d == *e)
125 d++;
126 else
127 d = local_text;
128 if (!*d) {
129 result = e + 1;
130 goto cleanup;
133 cleanup:
134 g_free (local_text);
135 return result;
138 /* Searches text in the buffer pointed by start. Search ends */
139 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
140 static const char *search_string_node (const char *start, const char *text)
142 const char *d = text;
143 const char *e = start;
145 if (!start)
146 return 0;
148 for (; *e && *e != CHAR_NODE_END; e++){
149 if (*d == *e)
150 d++;
151 else
152 d = text;
153 if (!*d)
154 return e+1;
156 return 0;
159 /* Searches the_char in the buffer pointer by start and searches */
160 /* it can search forward (direction = 1) or backward (direction = -1) */
161 static const char *search_char_node (const char *start, char the_char, int direction)
163 const char *e;
165 e = start;
167 for (; *e && (*e != CHAR_NODE_END); e += direction){
168 if (*e == the_char)
169 return e;
171 return 0;
174 /* Returns the new current pointer when moved lines lines */
175 static const char *move_forward2 (const char *c, int lines)
177 const char *p;
178 int line;
180 currentpoint = c;
181 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END;
182 str_cnext_char (&p)){
184 if (line == lines)
185 return currentpoint = p;
186 if (*p == '\n')
187 line++;
189 return currentpoint = c;
192 static const char *move_backward2 (const char *c, int lines)
194 const char *p;
195 int line;
197 currentpoint = c;
198 for (line = 0, p = currentpoint; *p && p >= data;
199 str_cprev_char (&p)) {
201 if (*p == CHAR_NODE_END)
203 /* We reached the beginning of the node */
204 /* Skip the node headers */
205 while (*p != ']') str_cnext_char (&p);
206 return currentpoint = p + 2; /* Skip the newline following the start of the node */
208 if (*(p - 1) == '\n')
209 line++;
210 if (line == lines)
211 return currentpoint = p;
213 return currentpoint = c;
216 static void move_forward (int i)
218 if (end_of_node)
219 return;
220 currentpoint = move_forward2 (currentpoint, i);
223 static void move_backward (int i)
225 currentpoint = move_backward2 (currentpoint, ++i);
228 static void move_to_top (void)
230 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
231 currentpoint--;
232 while (*currentpoint != ']')
233 currentpoint++;
234 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
235 selected_item = NULL;
238 static void move_to_bottom (void)
240 while (*currentpoint && *currentpoint != CHAR_NODE_END)
241 currentpoint++;
242 currentpoint--;
243 move_backward (help_lines - 1);
246 static const char *help_follow_link (const char *start, const char *selected_item)
248 char link_name [MAXLINKNAME];
249 const char *p;
250 int i = 0;
252 if (!selected_item)
253 return start;
255 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
257 if (*p == CHAR_LINK_POINTER){
258 link_name [0] = '[';
259 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
260 link_name [i++] = *++p;
261 link_name [i-1] = ']';
262 link_name [i] = 0;
263 p = search_string (data, link_name);
264 if (p) {
265 p += 1; /* Skip the newline following the start of the node */
266 return p;
270 /* Create a replacement page with the error message */
271 return _(" Help file format error\n");
274 static const char *select_next_link (const char *current_link)
276 const char *p;
278 if (!current_link)
279 return 0;
281 p = search_string_node (current_link, STRING_LINK_END);
282 if (!p)
283 return NULL;
284 p = search_string_node (p, STRING_LINK_START);
285 if (!p)
286 return NULL;
287 return p - 1;
290 static const char *select_prev_link (const char *current_link)
292 if (!current_link)
293 return 0;
295 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
298 static void start_link_area (int x, int y, const char *link_name)
300 Link_Area *new;
302 if (inside_link_area)
303 message (D_NORMAL, _("Warning"), _(" Internal bug: Double start of link area "));
305 /* Allocate memory for a new link area */
306 new = g_new (Link_Area, 1);
307 new->next = link_area;
308 link_area = new;
310 /* Save the beginning coordinates of the link area */
311 link_area->x1 = x;
312 link_area->y1 = y;
314 /* Save the name of the destination anchor */
315 link_area->link_name = link_name;
317 inside_link_area = 1;
320 static void end_link_area (int x, int y)
322 if (inside_link_area){
323 /* Save the end coordinates of the link area */
324 link_area->x2 = x;
325 link_area->y2 = y;
327 inside_link_area = 0;
331 static void clear_link_areas (void)
333 Link_Area *current;
335 while (link_area){
336 current = link_area;
337 link_area = current -> next;
338 g_free (current);
340 inside_link_area = 0;
343 static void help_show (Dlg_head *h, const char *paint_start)
345 const char *p, *n;
346 int col, line, c, w;
347 int painting = 1;
348 int acs; /* Flag: Alternate character set active? */
349 int repeat_paint;
350 int active_col, active_line;/* Active link position */
351 static char buff[MB_LEN_MAX + 1];
353 tty_setcolor (HELP_NORMAL_COLOR);
354 do {
356 line = col = acs = active_col = active_line = repeat_paint = 0;
358 clear_link_areas ();
359 if (selected_item < paint_start)
360 selected_item = NULL;
362 p = paint_start;
363 n = paint_start;
364 while (n[0] != '\0' && n[0] != CHAR_NODE_END && line < help_lines) {
365 p = n;
366 n = str_cget_next_char (p);
367 memcpy (buff, p, n - p);
368 buff[n - p] = '\0';
369 c = (unsigned char) buff[0];
371 switch (c){
372 case CHAR_LINK_START:
373 if (selected_item == NULL)
374 selected_item = p;
375 if (p == selected_item){
376 tty_setcolor (HELP_SLINK_COLOR);
378 /* Store the coordinates of the link */
379 active_col = col + 2;
380 active_line = line + 2;
382 else
383 tty_setcolor (HELP_LINK_COLOR);
384 start_link_area (col, line, p);
385 break;
386 case CHAR_LINK_POINTER:
387 painting = 0;
388 end_link_area (col - 1, line);
389 break;
390 case CHAR_LINK_END:
391 painting = 1;
392 tty_setcolor (HELP_NORMAL_COLOR);
393 break;
394 case CHAR_ALTERNATE:
395 acs = 1;
396 break;
397 case CHAR_NORMAL:
398 acs = 0;
399 break;
400 case CHAR_VERSION:
401 dlg_move (h, line+2, col+2);
402 tty_print_string (VERSION);
403 col += str_term_width1 (VERSION);
404 break;
405 case CHAR_FONT_BOLD:
406 tty_setcolor (HELP_BOLD_COLOR);
407 break;
408 case CHAR_FONT_ITALIC:
409 tty_setcolor (HELP_ITALIC_COLOR);
410 break;
411 case CHAR_FONT_NORMAL:
412 tty_setcolor (HELP_NORMAL_COLOR);
413 break;
414 case '\n':
415 line++;
416 col = 0;
417 break;
418 case '\t':
419 col = (col / 8 + 1) * 8;
420 break;
421 default:
422 if (!painting)
423 continue;
424 w = str_term_width1 (buff);
425 if (col + w > HELP_WINDOW_WIDTH)
426 continue;
428 dlg_move (h, line+2, col+2);
429 if (acs){
430 if (c == ' ' || c == '.')
431 tty_print_char (c);
432 else
433 #ifndef HAVE_SLANG
434 tty_print_char (acs_map [c]);
435 #else
436 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
437 #endif
438 } else {
439 tty_print_string (buff);
441 col+= w;
442 break;
445 last_shown = p;
446 end_of_node = line < help_lines;
447 tty_setcolor (HELP_NORMAL_COLOR);
448 if (selected_item >= last_shown){
449 if (link_area != NULL){
450 selected_item = link_area->link_name;
451 repeat_paint = 1;
453 else
454 selected_item = NULL;
456 } while (repeat_paint);
458 /* Position the cursor over a nice link */
459 if (active_col)
460 dlg_move (h, active_line, active_col);
463 static int
464 help_event (Gpm_Event *event, void *vp)
466 Widget *w = vp;
467 Link_Area *current_area;
469 if (! (event->type & GPM_UP))
470 return 0;
472 /* The event is relative to the dialog window, adjust it: */
473 event->x -= 2;
474 event->y -= 2;
476 if (event->buttons & GPM_B_RIGHT){
477 currentpoint = history [history_ptr].page;
478 selected_item = history [history_ptr].link;
479 history_ptr--;
480 if (history_ptr < 0)
481 history_ptr = HISTORY_SIZE-1;
483 help_callback (w->parent, DLG_DRAW, 0);
484 return 0;
487 /* Test whether the mouse click is inside one of the link areas */
488 current_area = link_area;
489 while (current_area)
491 /* Test one line link area */
492 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
493 event->y == current_area->y2 && event->x <= current_area->x2)
494 break;
495 /* Test two line link area */
496 if (current_area->y1 + 1 == current_area->y2){
497 /* The first line */
498 if (event->y == current_area->y1 && event->x >= current_area->x1)
499 break;
500 /* The second line */
501 if (event->y == current_area->y2 && event->x <= current_area->x2)
502 break;
504 /* Mouse will not work with link areas of more than two lines */
506 current_area = current_area -> next;
509 /* Test whether a link area was found */
510 if (current_area){
511 /* The click was inside a link area -> follow the link */
512 history_ptr = (history_ptr+1) % HISTORY_SIZE;
513 history [history_ptr].page = currentpoint;
514 history [history_ptr].link = current_area->link_name;
515 currentpoint = help_follow_link (currentpoint, current_area->link_name);
516 selected_item = NULL;
517 } else{
518 if (event->y < 0)
519 move_backward (help_lines - 1);
520 else if (event->y >= help_lines)
521 move_forward (help_lines - 1);
522 else if (event->y < help_lines/2)
523 move_backward (1);
524 else
525 move_forward (1);
528 /* Show the new node */
529 help_callback (w->parent, DLG_DRAW, 0);
531 return 0;
534 /* show help */
535 static void
536 help_help_cmd (void *vp)
538 Dlg_head *h = vp;
539 const char *p;
541 history_ptr = (history_ptr+1) % HISTORY_SIZE;
542 history [history_ptr].page = currentpoint;
543 history [history_ptr].link = selected_item;
545 p = search_string(data, "[How to use help]");
546 if (p == NULL)
547 return;
549 currentpoint = p + 1; /* Skip the newline following the start of the node */
550 selected_item = NULL;
551 help_callback (h, DLG_DRAW, 0);
554 static void
555 help_index_cmd (void *vp)
557 Dlg_head *h = vp;
558 const char *new_item;
560 if (!(new_item = search_string (data, "[Contents]"))) {
561 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
562 "[Contents]");
563 return;
566 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
567 history[history_ptr].page = currentpoint;
568 history[history_ptr].link = selected_item;
570 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
571 selected_item = NULL;
572 help_callback (h, DLG_DRAW, 0);
575 static void help_quit_cmd (void *vp)
577 dlg_stop ((Dlg_head *) vp);
580 static void prev_node_cmd (void *vp)
582 Dlg_head *h = vp;
583 currentpoint = history [history_ptr].page;
584 selected_item = history [history_ptr].link;
585 history_ptr--;
586 if (history_ptr < 0)
587 history_ptr = HISTORY_SIZE-1;
589 help_callback (h, DLG_DRAW, 0);
592 static cb_ret_t
593 md_callback (Widget *w, widget_msg_t msg, int parm)
595 switch (msg) {
596 case WIDGET_RESIZED:
597 w->lines = help_lines;
598 return MSG_HANDLED;
600 default:
601 return default_proc (msg, parm);
605 static Widget *
606 mousedispatch_new (int y, int x, int yl, int xl)
608 Widget *w = g_new (Widget, 1);
610 init_widget (w, y, x, yl, xl, md_callback, help_event);
612 return w;
615 static void help_cmk_move_backward(void *vp, int lines) {
616 (void) &vp;
617 move_backward(lines);
619 static void help_cmk_move_forward(void *vp, int lines) {
620 (void) &vp;
621 move_forward(lines);
623 static void help_cmk_moveto_top(void *vp, int lines) {
624 (void) &vp;
625 (void) &lines;
626 move_to_top();
628 static void help_cmk_moveto_bottom(void *vp, int lines) {
629 (void) &vp;
630 (void) &lines;
631 move_to_bottom();
634 static cb_ret_t
635 help_handle_key (struct Dlg_head *h, int c)
637 const char *new_item;
639 if (c != KEY_UP && c != KEY_DOWN &&
640 check_movement_keys (c, help_lines, NULL,
641 help_cmk_move_backward,
642 help_cmk_move_forward,
643 help_cmk_moveto_top,
644 help_cmk_moveto_bottom)) {
645 /* Nothing */;
646 } else switch (c){
647 case 'l':
648 case KEY_LEFT:
649 prev_node_cmd (h);
650 break;
652 case '\n':
653 case KEY_RIGHT:
654 /* follow link */
655 if (!selected_item){
656 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
657 /* Is there any reason why the right key would take us
658 * backward if there are no links selected?, I agree
659 * with Torben than doing nothing in this case is better
661 /* If there are no links, go backward in history */
662 history_ptr--;
663 if (history_ptr < 0)
664 history_ptr = HISTORY_SIZE-1;
666 currentpoint = history [history_ptr].page;
667 selected_item = history [history_ptr].link;
668 #endif
669 } else {
670 history_ptr = (history_ptr+1) % HISTORY_SIZE;
671 history [history_ptr].page = currentpoint;
672 history [history_ptr].link = selected_item;
673 currentpoint = help_follow_link (currentpoint, selected_item);
675 selected_item = NULL;
676 break;
678 case KEY_DOWN:
679 case '\t':
680 new_item = select_next_link (selected_item);
681 if (new_item){
682 selected_item = new_item;
683 if (selected_item >= last_shown){
684 if (c == KEY_DOWN)
685 move_forward (1);
686 else
687 selected_item = NULL;
689 } else if (c == KEY_DOWN)
690 move_forward (1);
691 else
692 selected_item = NULL;
693 break;
695 case KEY_UP:
696 case ALT ('\t'):
697 /* select previous link */
698 new_item = select_prev_link (selected_item);
699 selected_item = new_item;
700 if (selected_item == NULL || selected_item < currentpoint) {
701 if (c == KEY_UP)
702 move_backward (1);
703 else{
704 if (link_area != NULL)
705 selected_item = link_area->link_name;
706 else
707 selected_item = NULL;
710 break;
712 case 'n':
713 /* Next node */
714 new_item = currentpoint;
715 while (*new_item && *new_item != CHAR_NODE_END)
716 new_item++;
717 if (*++new_item == '['){
718 while (*++new_item) {
719 if (*new_item == ']' && *++new_item && *++new_item) {
720 currentpoint = new_item;
721 selected_item = NULL;
722 break;
726 break;
728 case 'p':
729 /* Previous node */
730 new_item = currentpoint;
731 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
732 new_item--;
733 new_item--;
734 while (new_item > data && *new_item != CHAR_NODE_END)
735 new_item--;
736 while (*new_item != ']')
737 new_item++;
738 currentpoint = new_item + 2;
739 selected_item = NULL;
740 break;
742 case 'c':
743 help_index_cmd (h);
744 break;
746 case ESC_CHAR:
747 case XCTRL('g'):
748 dlg_stop (h);
749 break;
751 default:
752 return MSG_NOT_HANDLED;
754 help_callback (h, DLG_DRAW, 0);
755 return MSG_HANDLED;
758 static cb_ret_t
759 help_callback (Dlg_head *h, dlg_msg_t msg, int parm)
761 WButtonBar *bb;
763 switch (msg) {
764 case DLG_RESIZE:
765 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
766 dlg_set_size (h, help_lines + 4, HELP_WINDOW_WIDTH + 4);
767 bb = find_buttonbar (h);
768 widget_set_size (&bb->widget, LINES - 1, 0, 1, COLS);
769 return MSG_HANDLED;
771 case DLG_DRAW:
772 common_dialog_repaint (h);
773 help_show (h, currentpoint);
774 return MSG_HANDLED;
776 case DLG_KEY:
777 return help_handle_key (h, parm);
779 default:
780 return default_dlg_callback (h, msg, parm);
784 static void
785 interactive_display_finish (void)
787 clear_link_areas ();
790 /* translate help file into terminal encoding */
791 static void
792 translate_file (char *filedata)
794 GIConv conv;
795 GString *translated_data;
797 translated_data = g_string_new ("");
799 conv = str_crt_conv_from ("UTF-8");
801 if (conv != INVALID_CONV) {
802 g_free (data);
804 if (str_convert (conv, filedata, translated_data) != ESTR_FAILURE) {
805 data = translated_data->str;
806 g_string_free (translated_data, FALSE);
807 } else {
808 data = NULL;
809 g_string_free (translated_data, TRUE);
811 str_close_conv (conv);
812 } else
813 g_string_free (translated_data, TRUE);
816 void
817 interactive_display (const char *filename, const char *node)
819 WButtonBar *help_bar;
820 Widget *md;
821 char *hlpfile = NULL;
822 char *filedata;
824 if (filename)
825 filedata = load_file (filename);
826 else
827 filedata = load_mc_home_file ("mc.hlp", &hlpfile);
829 if (filedata == NULL) {
830 message (D_ERROR, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
831 unix_error_string (errno));
834 if (!filename)
835 g_free (hlpfile);
837 if (filedata == NULL)
838 return;
840 translate_file (filedata);
842 g_free (filedata);
844 if (!data)
845 return;
847 if (!node || !*node)
848 node = "[main]";
850 if (!(main_node = search_string (data, node))) {
851 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
852 node);
854 /* Fallback to [main], return if it also cannot be found */
855 main_node = search_string (data, "[main]");
856 if (!main_node) {
857 interactive_display_finish ();
858 return;
862 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
864 whelp =
865 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
866 dialog_colors, help_callback, "[Help]", _("Help"),
867 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
869 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
870 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
872 for (history_ptr = HISTORY_SIZE; history_ptr;) {
873 history_ptr--;
874 history[history_ptr].page = currentpoint;
875 history[history_ptr].link = selected_item;
878 help_bar = buttonbar_new (1);
879 help_bar->widget.y -= whelp->y;
880 help_bar->widget.x -= whelp->x;
882 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
884 add_widget (whelp, md);
885 add_widget (whelp, help_bar);
887 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
888 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
889 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
890 buttonbar_clear_label (whelp, 4);
891 buttonbar_clear_label (whelp, 5);
892 buttonbar_clear_label (whelp, 6);
893 buttonbar_clear_label (whelp, 7);
894 buttonbar_clear_label (whelp, 8);
895 buttonbar_clear_label (whelp, 9);
896 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
898 run_dlg (whelp);
899 interactive_display_finish ();
900 destroy_dlg (whelp);