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.
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.
51 #include <sys/types.h>
56 #include "../src/tty/tty.h"
57 #include "../src/skin/skin.h"
58 #include "../src/tty/mouse.h"
59 #include "../src/tty/key.h"
61 #include "dialog.h" /* For Dlg_head */
62 #include "widget.h" /* For Widget */
63 #include "wtools.h" /* For common_dialog_repaint() */
69 const global_keymap_t
*help_map
;
71 #define MAXLINKNAME 80
72 #define HISTORY_SIZE 20
73 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
75 #define STRING_LINK_START "\01"
76 #define STRING_LINK_POINTER "\02"
77 #define STRING_LINK_END "\03"
78 #define STRING_NODE_END "\04"
81 static char *fdata
= NULL
; /* Pointer to the loaded data file */
82 static int help_lines
; /* Lines in help viewer */
83 static int history_ptr
; /* For the history queue */
84 static const char *main_node
; /* The main node */
85 static const char *last_shown
= NULL
; /* Last byte shown in a screen */
86 static gboolean end_of_node
= FALSE
; /* Flag: the last character of the node shown? */
87 static const char *currentpoint
;
88 static const char *selected_item
;
90 /* The widget variables */
91 static Dlg_head
*whelp
;
94 const char *page
; /* Pointer to the selected page */
95 const char *link
; /* Pointer to the selected link */
96 } history
[HISTORY_SIZE
];
98 /* Link areas for the mouse */
99 typedef struct Link_Area
{
101 const char *link_name
;
102 struct Link_Area
*next
;
105 static Link_Area
*link_area
= NULL
;
106 static gboolean inside_link_area
= FALSE
;
108 static cb_ret_t
help_callback (Dlg_head
*h
, Widget
*sender
,
109 dlg_msg_t msg
, int parm
, void *data
);
111 /* returns the position where text was found in the start buffer */
112 /* or 0 if not found */
114 search_string (const char *start
, const char *text
)
116 const char *result
= NULL
;
117 char *local_text
= g_strdup (text
);
118 char *d
= local_text
;
119 const char *e
= start
;
121 /* fmt sometimes replaces a space with a newline in the help file */
122 /* Replace the newlines in the link name with spaces to correct the situation */
130 for (d
= local_text
; *e
; e
++){
145 /* Searches text in the buffer pointed by start. Search ends */
146 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
148 search_string_node (const char *start
, const char *text
)
150 const char *d
= text
;
151 const char *e
= start
;
154 for (; *e
&& *e
!= CHAR_NODE_END
; e
++) {
166 /* Searches the_char in the buffer pointer by start and searches */
167 /* it can search forward (direction = 1) or backward (direction = -1) */
169 search_char_node (const char *start
, char the_char
, int direction
)
173 for (e
= start
; (*e
!= '\0') && (*e
!= CHAR_NODE_END
); e
+= direction
)
180 /* Returns the new current pointer when moved lines lines */
182 move_forward2 (const char *c
, int lines
)
188 for (line
= 0, p
= currentpoint
; (*p
!= '\0') && (*p
!= CHAR_NODE_END
);
189 str_cnext_char (&p
)) {
191 return currentpoint
= p
;
196 return currentpoint
= c
;
200 move_backward2 (const char *c
, int lines
)
206 for (line
= 0, p
= currentpoint
; (*p
!= '\0') && ((int) (p
- fdata
) >= 0);
207 str_cprev_char (&p
)) {
208 if (*p
== CHAR_NODE_END
) {
209 /* We reached the beginning of the node */
210 /* Skip the node headers */
213 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
216 if (*(p
- 1) == '\n')
219 return currentpoint
= p
;
221 return currentpoint
= c
;
228 currentpoint
= move_forward2 (currentpoint
, i
);
232 move_backward (int i
)
234 currentpoint
= move_backward2 (currentpoint
, ++i
);
240 while (((int) (currentpoint
> fdata
) > 0) && (*currentpoint
!= CHAR_NODE_END
))
243 while (*currentpoint
!= ']')
245 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
246 selected_item
= NULL
;
250 move_to_bottom (void)
252 while ((*currentpoint
!= '\0') && (*currentpoint
!= CHAR_NODE_END
))
255 move_backward (help_lines
- 1);
259 help_follow_link (const char *start
, const char *lc_selected_item
)
261 char link_name
[MAXLINKNAME
];
265 if (lc_selected_item
== NULL
)
268 for (p
= lc_selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
270 if (*p
== CHAR_LINK_POINTER
){
272 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
273 link_name
[i
++] = *++p
;
274 link_name
[i
- 1] = ']';
275 link_name
[i
] = '\0';
276 p
= search_string (fdata
, link_name
);
278 p
+= 1; /* Skip the newline following the start of the node */
283 /* Create a replacement page with the error message */
284 return _(" Help file format error\n");
288 select_next_link (const char *current_link
)
292 if (current_link
== NULL
)
295 p
= search_string_node (current_link
, STRING_LINK_END
);
298 p
= search_string_node (p
, STRING_LINK_START
);
305 select_prev_link (const char *current_link
)
307 return current_link
== NULL
309 : search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
313 start_link_area (int x
, int y
, const char *link_name
)
317 if (inside_link_area
)
318 message (D_NORMAL
, _("Warning"), _(" Internal bug: Double start of link area "));
320 /* Allocate memory for a new link area */
321 new = g_new (Link_Area
, 1);
322 new->next
= link_area
;
325 /* Save the beginning coordinates of the link area */
329 /* Save the name of the destination anchor */
330 link_area
->link_name
= link_name
;
332 inside_link_area
= TRUE
;
336 end_link_area (int x
, int y
)
338 if (inside_link_area
) {
339 /* Save the end coordinates of the link area */
342 inside_link_area
= FALSE
;
347 clear_link_areas (void)
351 while (link_area
!= NULL
) {
353 link_area
= current
->next
;
357 inside_link_area
= FALSE
;
361 help_show (Dlg_head
*h
, const char *paint_start
)
365 gboolean painting
= TRUE
;
366 gboolean acs
; /* Flag: Alternate character set active? */
367 gboolean repeat_paint
;
368 int active_col
, active_line
; /* Active link position */
369 static char buff
[MB_LEN_MAX
+ 1];
371 tty_setcolor (HELP_NORMAL_COLOR
);
373 line
= col
= active_col
= active_line
= 0;
374 repeat_paint
= FALSE
;
378 if ((int) (selected_item
- paint_start
) < 0)
379 selected_item
= NULL
;
383 while (n
[0] != '\0' && n
[0] != CHAR_NODE_END
&& line
< help_lines
) {
385 n
= str_cget_next_char (p
);
386 memcpy (buff
, p
, n
- p
);
389 c
= (unsigned char) buff
[0];
391 case CHAR_LINK_START
:
392 if (selected_item
== NULL
)
394 if (p
== selected_item
){
395 tty_setcolor (HELP_SLINK_COLOR
);
397 /* Store the coordinates of the link */
398 active_col
= col
+ 2;
399 active_line
= line
+ 2;
402 tty_setcolor (HELP_LINK_COLOR
);
403 start_link_area (col
, line
, p
);
405 case CHAR_LINK_POINTER
:
407 end_link_area (col
- 1, line
);
411 tty_setcolor (HELP_NORMAL_COLOR
);
420 dlg_move (h
, line
+2, col
+2);
421 tty_print_string (VERSION
);
422 col
+= str_term_width1 (VERSION
);
425 tty_setcolor (HELP_BOLD_COLOR
);
427 case CHAR_FONT_ITALIC
:
428 tty_setcolor (HELP_ITALIC_COLOR
);
430 case CHAR_FONT_NORMAL
:
431 tty_setcolor (HELP_NORMAL_COLOR
);
438 col
= (col
/ 8 + 1) * 8;
443 w
= str_term_width1 (buff
);
444 if (col
+ w
> HELP_WINDOW_WIDTH
)
447 dlg_move (h
, line
+ 2, col
+ 2);
449 tty_print_string (buff
);
450 else if (c
== ' ' || c
== '.')
454 tty_print_char (acs_map
[c
]);
456 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
464 end_of_node
= line
< help_lines
;
465 tty_setcolor (HELP_NORMAL_COLOR
);
466 if ((int) (selected_item
- last_shown
) >= 0) {
467 if (link_area
== NULL
)
468 selected_item
= NULL
;
470 selected_item
= link_area
->link_name
;
474 } while (repeat_paint
);
476 /* Position the cursor over a nice link */
478 dlg_move (h
, active_line
, active_col
);
482 help_event (Gpm_Event
*event
, void *vp
)
485 Link_Area
*current_area
;
487 if ((event
->type
& GPM_UP
) == 0)
490 /* The event is relative to the dialog window, adjust it: */
494 if (event
->buttons
& GPM_B_RIGHT
){
495 currentpoint
= history
[history_ptr
].page
;
496 selected_item
= history
[history_ptr
].link
;
499 history_ptr
= HISTORY_SIZE
-1;
501 help_callback (w
->parent
, NULL
, DLG_DRAW
, 0, NULL
);
505 /* Test whether the mouse click is inside one of the link areas */
506 current_area
= link_area
;
507 while (current_area
!= NULL
)
509 /* Test one line link area */
510 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
511 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
513 /* Test two line link area */
514 if (current_area
->y1
+ 1 == current_area
->y2
){
516 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
518 /* The second line */
519 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
522 /* Mouse will not work with link areas of more than two lines */
524 current_area
= current_area
->next
;
527 /* Test whether a link area was found */
528 if (current_area
!= NULL
) {
529 /* The click was inside a link area -> follow the link */
530 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
531 history
[history_ptr
].page
= currentpoint
;
532 history
[history_ptr
].link
= current_area
->link_name
;
533 currentpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
534 selected_item
= NULL
;
535 } else if (event
->y
< 0)
536 move_backward (help_lines
- 1);
537 else if (event
->y
>= help_lines
)
538 move_forward (help_lines
- 1);
539 else if (event
->y
< help_lines
/2)
544 /* Show the new node */
545 help_callback (w
->parent
, NULL
, DLG_DRAW
, 0, NULL
);
552 help_help (Dlg_head
*h
)
556 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
557 history
[history_ptr
].page
= currentpoint
;
558 history
[history_ptr
].link
= selected_item
;
560 p
= search_string (fdata
, "[How to use help]");
562 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
563 selected_item
= NULL
;
564 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
569 help_index (Dlg_head
*h
)
571 const char *new_item
;
573 new_item
= search_string (fdata
, "[Contents]");
575 if (new_item
== NULL
)
576 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
579 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
580 history
[history_ptr
].page
= currentpoint
;
581 history
[history_ptr
].link
= selected_item
;
583 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
584 selected_item
= NULL
;
585 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
590 help_back (Dlg_head
*h
)
592 currentpoint
= history
[history_ptr
].page
;
593 selected_item
= history
[history_ptr
].link
;
596 history_ptr
= HISTORY_SIZE
- 1;
598 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
); /* FIXME: unneeded? */
602 help_cmk_move_backward(void *vp
, int lines
)
605 move_backward (lines
);
609 help_cmk_move_forward(void *vp
, int lines
)
612 move_forward (lines
);
616 help_cmk_moveto_top(void *vp
, int lines
)
624 help_cmk_moveto_bottom(void *vp
, int lines
)
632 help_next_link (gboolean move_down
)
634 const char *new_item
;
636 new_item
= select_next_link (selected_item
);
637 if (new_item
!= NULL
) {
638 selected_item
= new_item
;
639 if ((int) (selected_item
- last_shown
) >= 0) {
643 selected_item
= NULL
;
645 } else if (move_down
)
648 selected_item
= NULL
;
652 help_prev_link (gboolean move_up
)
654 const char *new_item
;
656 new_item
= select_prev_link (selected_item
);
657 selected_item
= new_item
;
658 if ((selected_item
== NULL
) || (selected_item
< currentpoint
)) {
661 else if (link_area
!= NULL
)
662 selected_item
= link_area
->link_name
;
664 selected_item
= NULL
;
669 help_next_node (void)
671 const char *new_item
;
673 new_item
= currentpoint
;
674 while ((*new_item
!= '\0') && (*new_item
!= CHAR_NODE_END
))
677 if (*++new_item
== '[')
678 while (*++new_item
!= '\0')
679 if ((*new_item
== ']') && (*++new_item
!= '\0')
680 && (*++new_item
!= '\0')) {
681 currentpoint
= new_item
;
682 selected_item
= NULL
;
688 help_prev_node (void)
690 const char *new_item
;
692 new_item
= currentpoint
;
693 while (((int) (new_item
- fdata
) > 1) && (*new_item
!= CHAR_NODE_END
))
696 while (((int) (new_item
- fdata
) > 0) && (*new_item
!= CHAR_NODE_END
))
698 while (*new_item
!= ']')
700 currentpoint
= new_item
+ 2;
701 selected_item
= NULL
;
705 help_select_link (void)
708 if (selected_item
== NULL
) {
709 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
710 /* Is there any reason why the right key would take us
711 * backward if there are no links selected?, I agree
712 * with Torben than doing nothing in this case is better
714 /* If there are no links, go backward in history */
717 history_ptr
= HISTORY_SIZE
-1;
719 currentpoint
= history
[history_ptr
].page
;
720 selected_item
= history
[history_ptr
].link
;
723 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
724 history
[history_ptr
].page
= currentpoint
;
725 history
[history_ptr
].link
= selected_item
;
726 currentpoint
= help_follow_link (currentpoint
, selected_item
);
729 selected_item
= NULL
;
733 help_execute_cmd (unsigned long command
)
735 cb_ret_t ret
= MSG_HANDLED
;
748 help_prev_link (TRUE
);
750 case CK_HelpMoveDown
:
751 help_next_link (TRUE
);
753 case CK_HelpSelectLink
:
756 case CK_HelpNextLink
:
757 help_next_link (FALSE
);
759 case CK_HelpPrevLink
:
760 help_prev_link (FALSE
);
762 case CK_HelpNextNode
:
765 case CK_HelpPrevNode
:
772 ret
= MSG_NOT_HANDLED
;
779 help_handle_key (Dlg_head
*h
, int c
)
781 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
782 check_movement_keys (c
, help_lines
, NULL
,
783 help_cmk_move_backward
,
784 help_cmk_move_forward
,
786 help_cmk_moveto_bottom
) == MSG_HANDLED
) {
789 unsigned long command
;
791 command
= lookup_keymap_command (help_map
, c
);
792 if ((command
== CK_Ignore_Key
)
793 || (help_execute_cmd (command
) == MSG_NOT_HANDLED
))
794 return MSG_NOT_HANDLED
;
797 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
802 help_callback (Dlg_head
*h
, Widget
*sender
,
803 dlg_msg_t msg
, int parm
, void *data
)
809 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
810 dlg_set_size (h
, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4);
811 bb
= find_buttonbar (h
);
812 widget_set_size (&bb
->widget
, LINES
- 1, 0, 1, COLS
);
816 common_dialog_repaint (h
);
817 help_show (h
, currentpoint
);
821 return help_handle_key (h
, parm
);
824 /* command from buttonbar */
825 return help_execute_cmd (parm
);
828 return default_dlg_callback (h
, sender
, msg
, parm
, data
);
833 interactive_display_finish (void)
838 /* translate help file into terminal encoding */
840 translate_file (char *filedata
)
843 GString
*translated_data
;
845 translated_data
= g_string_new ("");
847 conv
= str_crt_conv_from ("UTF-8");
849 if (conv
== INVALID_CONV
)
850 g_string_free (translated_data
, TRUE
);
854 if (str_convert (conv
, filedata
, translated_data
) != ESTR_FAILURE
) {
855 fdata
= translated_data
->str
;
856 g_string_free (translated_data
, FALSE
);
859 g_string_free (translated_data
, TRUE
);
861 str_close_conv (conv
);
866 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
870 w
->lines
= help_lines
;
874 return default_proc (msg
, parm
);
879 mousedispatch_new (int y
, int x
, int yl
, int xl
)
881 Widget
*w
= g_new (Widget
, 1);
882 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
887 interactive_display (const char *filename
, const char *node
)
889 WButtonBar
*help_bar
;
891 char *hlpfile
= NULL
;
894 if (filename
!= NULL
)
895 filedata
= load_file (filename
);
897 filedata
= load_mc_home_file ("mc.hlp", &hlpfile
);
899 if (filedata
== NULL
)
900 message (D_ERROR
, MSG_ERROR
, _(" Cannot open file %s \n %s "), filename
? filename
: hlpfile
,
901 unix_error_string (errno
));
905 if (filedata
== NULL
)
908 translate_file (filedata
);
915 if ((node
== NULL
) || (*node
== '\0'))
918 main_node
= search_string (fdata
, node
);
920 if (main_node
== NULL
) {
921 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
924 /* Fallback to [main], return if it also cannot be found */
925 main_node
= search_string (fdata
, "[main]");
926 if (main_node
== NULL
) {
927 interactive_display_finish ();
932 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
935 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
936 dialog_colors
, help_callback
, "[Help]", _("Help"),
937 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
939 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
940 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
942 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
944 history
[history_ptr
].page
= currentpoint
;
945 history
[history_ptr
].link
= selected_item
;
948 help_bar
= buttonbar_new (TRUE
);
949 help_bar
->widget
.y
-= whelp
->y
;
950 help_bar
->widget
.x
-= whelp
->x
;
952 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
954 add_widget (whelp
, md
);
955 add_widget (whelp
, help_bar
);
957 buttonbar_set_label (help_bar
, 1, Q_("ButtonBar|Help"), help_map
, NULL
);
958 buttonbar_set_label (help_bar
, 2, Q_("ButtonBar|Index"), help_map
, NULL
);
959 buttonbar_set_label (help_bar
, 3, Q_("ButtonBar|Prev"), help_map
, NULL
);
960 buttonbar_set_label (help_bar
, 4, "", help_map
, NULL
);
961 buttonbar_set_label (help_bar
, 5, "", help_map
, NULL
);
962 buttonbar_set_label (help_bar
, 6, "", help_map
, NULL
);
963 buttonbar_set_label (help_bar
, 7, "", help_map
, NULL
);
964 buttonbar_set_label (help_bar
, 8, "", help_map
, NULL
);
965 buttonbar_set_label (help_bar
, 9, "", help_map
, NULL
);
966 buttonbar_set_label (help_bar
, 10, Q_("ButtonBar|Quit"), help_map
, NULL
);
969 interactive_display_finish ();