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>
54 #include "lib/global.h"
56 #include "lib/tty/tty.h"
57 #include "lib/tty/mouse.h"
59 #include "lib/strutil.h"
61 #include "dialog.h" /* For Dlg_head */
62 #include "widget.h" /* For Widget */
63 #include "wtools.h" /* For common_dialog_repaint() */
69 #define MAXLINKNAME 80
70 #define HISTORY_SIZE 20
71 #define HELP_WINDOW_WIDTH min(80, COLS - 16)
73 #define STRING_LINK_START "\01"
74 #define STRING_LINK_POINTER "\02"
75 #define STRING_LINK_END "\03"
76 #define STRING_NODE_END "\04"
79 static char *fdata
= NULL
; /* Pointer to the loaded data file */
80 static int help_lines
; /* Lines in help viewer */
81 static int history_ptr
; /* For the history queue */
82 static const char *main_node
; /* The main node */
83 static const char *last_shown
= NULL
; /* Last byte shown in a screen */
84 static gboolean end_of_node
= FALSE
; /* Flag: the last character of the node shown? */
85 static const char *currentpoint
;
86 static const char *selected_item
;
88 /* The widget variables */
89 static Dlg_head
*whelp
;
93 const char *page
; /* Pointer to the selected page */
94 const char *link
; /* Pointer to the selected link */
95 } history
[HISTORY_SIZE
];
97 /* Link areas for the mouse */
98 typedef struct Link_Area
101 const char *link_name
;
104 static GSList
*link_area
= NULL
;
105 static gboolean inside_link_area
= FALSE
;
107 static cb_ret_t
help_callback (Dlg_head
* h
, Widget
* sender
, dlg_msg_t msg
, int parm
, void *data
);
109 /* returns the position where text was found in the start buffer */
110 /* or 0 if not found */
112 search_string (const char *start
, const char *text
)
114 const char *result
= NULL
;
115 char *local_text
= g_strdup (text
);
116 char *d
= local_text
;
117 const char *e
= start
;
119 /* fmt sometimes replaces a space with a newline in the help file */
120 /* Replace the newlines in the link name with spaces to correct the situation */
129 for (d
= local_text
; *e
; e
++)
146 /* Searches text in the buffer pointed by start. Search ends */
147 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
149 search_string_node (const char *start
, const char *text
)
151 const char *d
= text
;
152 const char *e
= start
;
155 for (; *e
&& *e
!= CHAR_NODE_END
; e
++)
168 /* Searches the_char in the buffer pointer by start and searches */
169 /* it can search forward (direction = 1) or backward (direction = -1) */
171 search_char_node (const char *start
, char the_char
, int direction
)
175 for (e
= start
; (*e
!= '\0') && (*e
!= CHAR_NODE_END
); e
+= direction
)
182 /* Returns the new current pointer when moved lines lines */
184 move_forward2 (const char *c
, int lines
)
190 for (line
= 0, p
= currentpoint
; (*p
!= '\0') && (*p
!= CHAR_NODE_END
); str_cnext_char (&p
))
193 return currentpoint
= p
;
198 return currentpoint
= c
;
202 move_backward2 (const char *c
, int lines
)
208 for (line
= 0, p
= currentpoint
; (*p
!= '\0') && ((int) (p
- fdata
) >= 0); str_cprev_char (&p
))
210 if (*p
== CHAR_NODE_END
)
212 /* We reached the beginning of the node */
213 /* Skip the node headers */
216 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
219 if (*(p
- 1) == '\n')
222 return currentpoint
= p
;
224 return currentpoint
= c
;
231 currentpoint
= move_forward2 (currentpoint
, i
);
235 move_backward (int i
)
237 currentpoint
= move_backward2 (currentpoint
, ++i
);
243 while (((int) (currentpoint
> fdata
) > 0) && (*currentpoint
!= CHAR_NODE_END
))
246 while (*currentpoint
!= ']')
248 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
249 selected_item
= NULL
;
253 move_to_bottom (void)
255 while ((*currentpoint
!= '\0') && (*currentpoint
!= CHAR_NODE_END
))
262 help_follow_link (const char *start
, const char *lc_selected_item
)
264 char link_name
[MAXLINKNAME
];
268 if (lc_selected_item
== NULL
)
271 for (p
= lc_selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
273 if (*p
== CHAR_LINK_POINTER
)
276 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
- 3;)
277 link_name
[i
++] = *++p
;
278 link_name
[i
- 1] = ']';
280 p
= search_string (fdata
, link_name
);
283 p
+= 1; /* Skip the newline following the start of the node */
288 /* Create a replacement page with the error message */
289 return _("Help file format error\n");
293 select_next_link (const char *current_link
)
297 if (current_link
== NULL
)
300 p
= search_string_node (current_link
, STRING_LINK_END
);
303 p
= search_string_node (p
, STRING_LINK_START
);
310 select_prev_link (const char *current_link
)
312 return current_link
== NULL
? NULL
: search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
316 start_link_area (int x
, int y
, const char *link_name
)
320 if (inside_link_area
)
321 message (D_NORMAL
, _("Warning"), _("Internal bug: Double start of link area"));
323 /* Allocate memory for a new link area */
324 la
= g_new (Link_Area
, 1);
325 /* Save the beginning coordinates of the link area */
328 /* Save the name of the destination anchor */
329 la
->link_name
= link_name
;
330 link_area
= g_slist_prepend (link_area
, la
);
332 inside_link_area
= TRUE
;
336 end_link_area (int x
, int y
)
338 if (inside_link_area
)
340 Link_Area
*la
= (Link_Area
*) link_area
->data
;
341 /* Save the end coordinates of the link area */
344 inside_link_area
= FALSE
;
349 clear_link_areas (void)
351 g_slist_foreach (link_area
, (GFunc
) g_free
, NULL
);
352 g_slist_free (link_area
);
354 inside_link_area
= FALSE
;
358 help_print_word (Dlg_head
* h
, GString
* word
, int *col
, int *line
, gboolean add_space
)
360 if (*line
>= help_lines
)
361 g_string_set_size (word
, 0);
366 w
= str_term_width1 (word
->str
);
367 if (*col
+ w
>= HELP_WINDOW_WIDTH
)
373 if (*line
>= help_lines
)
374 g_string_set_size (word
, 0);
377 dlg_move (h
, *line
+ 2, *col
+ 2);
378 tty_print_string (word
->str
);
379 g_string_set_size (word
, 0);
386 if (*col
< HELP_WINDOW_WIDTH
- 1)
388 tty_print_char (' ');
400 help_show (Dlg_head
* h
, const char *paint_start
)
404 gboolean painting
= TRUE
;
405 gboolean acs
; /* Flag: Alternate character set active? */
406 gboolean repeat_paint
;
407 int active_col
, active_line
; /* Active link position */
408 char buff
[MB_LEN_MAX
+ 1];
411 word
= g_string_sized_new (32);
413 tty_setcolor (HELP_NORMAL_COLOR
);
416 line
= col
= active_col
= active_line
= 0;
417 repeat_paint
= FALSE
;
421 if ((int) (selected_item
- paint_start
) < 0)
422 selected_item
= NULL
;
426 while ((n
[0] != '\0') && (n
[0] != CHAR_NODE_END
) && (line
< help_lines
))
429 n
= str_cget_next_char (p
);
430 memcpy (buff
, p
, n
- p
);
433 c
= (unsigned char) buff
[0];
436 case CHAR_LINK_START
:
437 if (selected_item
== NULL
)
439 if (p
!= selected_item
)
440 tty_setcolor (HELP_LINK_COLOR
);
443 tty_setcolor (HELP_SLINK_COLOR
);
445 /* Store the coordinates of the link */
446 active_col
= col
+ 2;
447 active_line
= line
+ 2;
449 start_link_area (col
, line
, p
);
451 case CHAR_LINK_POINTER
:
453 end_link_area (col
- 1, line
);
457 help_print_word (h
, word
, &col
, &line
, FALSE
);
458 tty_setcolor (HELP_NORMAL_COLOR
);
467 dlg_move (h
, line
+ 2, col
+ 2);
468 tty_print_string (VERSION
);
469 col
+= str_term_width1 (VERSION
);
472 tty_setcolor (HELP_BOLD_COLOR
);
474 case CHAR_FONT_ITALIC
:
475 tty_setcolor (HELP_ITALIC_COLOR
);
477 case CHAR_FONT_NORMAL
:
478 help_print_word (h
, word
, &col
, &line
, FALSE
);
479 tty_setcolor (HELP_NORMAL_COLOR
);
483 help_print_word (h
, word
, &col
, &line
, FALSE
);
488 col
= (col
/ 8 + 1) * 8;
489 if (col
>= HELP_WINDOW_WIDTH
)
498 help_print_word (h
, word
, &col
, &line
, TRUE
);
501 if (painting
&& (line
< help_lines
))
504 /* accumulate symbols in a word */
505 g_string_append (word
, buff
);
506 else if (col
< HELP_WINDOW_WIDTH
)
508 dlg_move (h
, line
+ 2, col
+ 2);
510 if ((c
== ' ') || (c
== '.'))
514 tty_print_char (acs_map
[c
]);
516 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
524 /* print last word */
525 if (n
[0] == CHAR_NODE_END
)
526 help_print_word (h
, word
, &col
, &line
, FALSE
);
529 end_of_node
= line
< help_lines
;
530 tty_setcolor (HELP_NORMAL_COLOR
);
531 if ((int) (selected_item
- last_shown
) >= 0)
533 if ((link_area
== NULL
) || (link_area
->data
== NULL
))
534 selected_item
= NULL
;
537 selected_item
= ((Link_Area
*) link_area
->data
)->link_name
;
542 while (repeat_paint
);
544 g_string_free (word
, TRUE
);
546 /* Position the cursor over a nice link */
548 dlg_move (h
, active_line
, active_col
);
552 help_event (Gpm_Event
* event
, void *vp
)
555 GSList
*current_area
;
557 if ((event
->type
& GPM_UP
) == 0)
560 /* The event is relative to the dialog window, adjust it: */
564 if (event
->buttons
& GPM_B_RIGHT
)
566 currentpoint
= history
[history_ptr
].page
;
567 selected_item
= history
[history_ptr
].link
;
570 history_ptr
= HISTORY_SIZE
- 1;
572 help_callback (w
->owner
, NULL
, DLG_DRAW
, 0, NULL
);
576 /* Test whether the mouse click is inside one of the link areas */
577 for (current_area
= link_area
; current_area
!= NULL
; current_area
= g_slist_next (current_area
))
579 Link_Area
*la
= (Link_Area
*) current_area
->data
;
580 /* Test one line link area */
581 if (event
->y
== la
->y1
&& event
->x
>= la
->x1
&& event
->y
== la
->y2
&& event
->x
<= la
->x2
)
583 /* Test two line link area */
584 if (la
->y1
+ 1 == la
->y2
)
587 if (event
->y
== la
->y1
&& event
->x
>= la
->x1
)
589 /* The second line */
590 if (event
->y
== la
->y2
&& event
->x
<= la
->x2
)
593 /* Mouse will not work with link areas of more than two lines */
596 /* Test whether a link area was found */
597 if (current_area
!= NULL
)
599 Link_Area
*la
= (Link_Area
*) current_area
->data
;
601 /* The click was inside a link area -> follow the link */
602 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
603 history
[history_ptr
].page
= currentpoint
;
604 history
[history_ptr
].link
= la
->link_name
;
605 currentpoint
= help_follow_link (currentpoint
, la
->link_name
);
606 selected_item
= NULL
;
608 else if (event
->y
< 0)
609 move_backward (help_lines
- 1);
610 else if (event
->y
>= help_lines
)
611 move_forward (help_lines
- 1);
612 else if (event
->y
< help_lines
/ 2)
617 /* Show the new node */
618 help_callback (w
->owner
, NULL
, DLG_DRAW
, 0, NULL
);
625 help_help (Dlg_head
* h
)
629 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
630 history
[history_ptr
].page
= currentpoint
;
631 history
[history_ptr
].link
= selected_item
;
633 p
= search_string (fdata
, "[How to use help]");
636 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
637 selected_item
= NULL
;
638 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
643 help_index (Dlg_head
* h
)
645 const char *new_item
;
647 new_item
= search_string (fdata
, "[Contents]");
649 if (new_item
== NULL
)
650 message (D_ERROR
, MSG_ERROR
, _("Cannot find node %s in help file"), "[Contents]");
653 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
654 history
[history_ptr
].page
= currentpoint
;
655 history
[history_ptr
].link
= selected_item
;
657 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
658 selected_item
= NULL
;
659 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
664 help_back (Dlg_head
* h
)
666 currentpoint
= history
[history_ptr
].page
;
667 selected_item
= history
[history_ptr
].link
;
670 history_ptr
= HISTORY_SIZE
- 1;
672 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
); /* FIXME: unneeded? */
676 help_next_link (gboolean move_down
)
678 const char *new_item
;
680 new_item
= select_next_link (selected_item
);
681 if (new_item
!= NULL
)
683 selected_item
= new_item
;
684 if ((int) (selected_item
- last_shown
) >= 0)
689 selected_item
= NULL
;
695 selected_item
= NULL
;
699 help_prev_link (gboolean move_up
)
701 const char *new_item
;
703 new_item
= select_prev_link (selected_item
);
704 selected_item
= new_item
;
705 if ((selected_item
== NULL
) || (selected_item
< currentpoint
))
709 else if ((link_area
!= NULL
) && (link_area
->data
!= NULL
))
710 selected_item
= ((Link_Area
*) link_area
->data
)->link_name
;
712 selected_item
= NULL
;
717 help_next_node (void)
719 const char *new_item
;
721 new_item
= currentpoint
;
722 while ((*new_item
!= '\0') && (*new_item
!= CHAR_NODE_END
))
725 if (*++new_item
== '[')
726 while (*++new_item
!= '\0')
727 if ((*new_item
== ']') && (*++new_item
!= '\0') && (*++new_item
!= '\0'))
729 currentpoint
= new_item
;
730 selected_item
= NULL
;
736 help_prev_node (void)
738 const char *new_item
;
740 new_item
= currentpoint
;
741 while (((int) (new_item
- fdata
) > 1) && (*new_item
!= CHAR_NODE_END
))
744 while (((int) (new_item
- fdata
) > 0) && (*new_item
!= CHAR_NODE_END
))
746 while (*new_item
!= ']')
748 currentpoint
= new_item
+ 2;
749 selected_item
= NULL
;
753 help_select_link (void)
756 if (selected_item
== NULL
)
758 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
759 /* Is there any reason why the right key would take us
760 * backward if there are no links selected?, I agree
761 * with Torben than doing nothing in this case is better
763 /* If there are no links, go backward in history */
766 history_ptr
= HISTORY_SIZE
- 1;
768 currentpoint
= history
[history_ptr
].page
;
769 selected_item
= history
[history_ptr
].link
;
774 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
775 history
[history_ptr
].page
= currentpoint
;
776 history
[history_ptr
].link
= selected_item
;
777 currentpoint
= help_follow_link (currentpoint
, selected_item
);
780 selected_item
= NULL
;
784 help_execute_cmd (unsigned long command
)
786 cb_ret_t ret
= MSG_HANDLED
;
800 help_prev_link (TRUE
);
802 case CK_HelpMoveDown
:
803 help_next_link (TRUE
);
805 case CK_HelpMovePgDn
:
806 move_forward (help_lines
- 1);
808 case CK_HelpMovePgUp
:
809 move_backward (help_lines
- 1);
811 case CK_HelpMoveHalfPgDn
:
812 move_forward (help_lines
/ 2);
814 case CK_HelpMoveHalfPgUp
:
815 move_backward (help_lines
/ 2);
820 case CK_HelpMoveBottom
:
823 case CK_HelpSelectLink
:
826 case CK_HelpNextLink
:
827 help_next_link (FALSE
);
829 case CK_HelpPrevLink
:
830 help_prev_link (FALSE
);
832 case CK_HelpNextNode
:
835 case CK_HelpPrevNode
:
842 ret
= MSG_NOT_HANDLED
;
849 help_handle_key (Dlg_head
* h
, int c
)
851 unsigned long command
;
853 command
= lookup_keymap_command (help_map
, c
);
854 if ((command
== CK_Ignore_Key
) || (help_execute_cmd (command
) == MSG_NOT_HANDLED
))
855 return MSG_NOT_HANDLED
;
857 help_callback (h
, NULL
, DLG_DRAW
, 0, NULL
);
862 help_callback (Dlg_head
* h
, Widget
* sender
, dlg_msg_t msg
, int parm
, void *data
)
869 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
870 dlg_set_size (h
, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4);
871 bb
= find_buttonbar (h
);
872 widget_set_size (&bb
->widget
, LINES
- 1, 0, 1, COLS
);
876 common_dialog_repaint (h
);
877 help_show (h
, currentpoint
);
881 return help_handle_key (h
, parm
);
884 /* command from buttonbar */
885 return help_execute_cmd (parm
);
888 return default_dlg_callback (h
, sender
, msg
, parm
, data
);
893 interactive_display_finish (void)
898 /* translate help file into terminal encoding */
900 translate_file (char *filedata
)
903 GString
*translated_data
;
905 /* initial allocation for largest whole help file */
906 translated_data
= g_string_sized_new (32 * 1024);
908 conv
= str_crt_conv_from ("UTF-8");
910 if (conv
== INVALID_CONV
)
911 g_string_free (translated_data
, TRUE
);
916 if (str_convert (conv
, filedata
, translated_data
) != ESTR_FAILURE
)
917 fdata
= g_string_free (translated_data
, FALSE
);
921 g_string_free (translated_data
, TRUE
);
923 str_close_conv (conv
);
928 md_callback (Widget
* w
, widget_msg_t msg
, int parm
)
933 w
->lines
= help_lines
;
937 return default_proc (msg
, parm
);
942 mousedispatch_new (int y
, int x
, int yl
, int xl
)
944 Widget
*w
= g_new (Widget
, 1);
945 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
950 interactive_display (const char *filename
, const char *node
)
952 const int help_colors
[DLG_COLOR_NUM
] = {
953 HELP_NORMAL_COLOR
, /* common text color */
954 0, /* unused in help */
955 HELP_BOLD_COLOR
, /* title color */
956 0 /* unused in help */
959 WButtonBar
*help_bar
;
961 char *hlpfile
= NULL
;
964 if (filename
!= NULL
)
965 filedata
= load_file (filename
);
967 filedata
= load_mc_home_file (mc_home
, mc_home_alt
, "mc.hlp", &hlpfile
);
969 if (filedata
== NULL
)
970 message (D_ERROR
, MSG_ERROR
, _("Cannot open file %s\n%s"),
971 filename
? filename
: hlpfile
, unix_error_string (errno
));
975 if (filedata
== NULL
)
978 translate_file (filedata
);
985 if ((node
== NULL
) || (*node
== '\0'))
988 main_node
= search_string (fdata
, node
);
990 if (main_node
== NULL
)
992 message (D_ERROR
, MSG_ERROR
, _("Cannot find node %s in help file"), node
);
994 /* Fallback to [main], return if it also cannot be found */
995 main_node
= search_string (fdata
, "[main]");
996 if (main_node
== NULL
)
998 interactive_display_finish ();
1003 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
1006 create_dlg (TRUE
, 0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
1007 help_colors
, help_callback
, "[Help]", _("Help"),
1008 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
1010 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
1011 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
1013 for (history_ptr
= HISTORY_SIZE
; history_ptr
;)
1016 history
[history_ptr
].page
= currentpoint
;
1017 history
[history_ptr
].link
= selected_item
;
1020 help_bar
= buttonbar_new (TRUE
);
1021 help_bar
->widget
.y
-= whelp
->y
;
1022 help_bar
->widget
.x
-= whelp
->x
;
1024 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
1026 add_widget (whelp
, md
);
1027 add_widget (whelp
, help_bar
);
1029 buttonbar_set_label (help_bar
, 1, Q_ ("ButtonBar|Help"), help_map
, NULL
);
1030 buttonbar_set_label (help_bar
, 2, Q_ ("ButtonBar|Index"), help_map
, NULL
);
1031 buttonbar_set_label (help_bar
, 3, Q_ ("ButtonBar|Prev"), help_map
, NULL
);
1032 buttonbar_set_label (help_bar
, 4, "", help_map
, NULL
);
1033 buttonbar_set_label (help_bar
, 5, "", help_map
, NULL
);
1034 buttonbar_set_label (help_bar
, 6, "", help_map
, NULL
);
1035 buttonbar_set_label (help_bar
, 7, "", help_map
, NULL
);
1036 buttonbar_set_label (help_bar
, 8, "", help_map
, NULL
);
1037 buttonbar_set_label (help_bar
, 9, "", help_map
, NULL
);
1038 buttonbar_set_label (help_bar
, 10, Q_ ("ButtonBar|Quit"), help_map
, NULL
);
1041 interactive_display_finish ();
1042 destroy_dlg (whelp
);