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 Implements the hypertext file viewer.
22 The hypertext file is a file that may have one or more nodes. Each
23 node ends with a ^D character and starts with a bracket, then the
24 name of the node and then a closing bracket. Right after the closing
25 bracket a newline is placed. This newline is not to be displayed by
26 the help viewer and must be skipped - its sole purpose is to faciliate
27 the work of the people managing the help file template (xnc.hlp) .
29 Links in the hypertext file are specified like this: the text that
30 will be highlighted should have a leading ^A, then it comes the
31 text, then a ^B indicating that highlighting is done, then the name
32 of the node you want to link to and then a ^C.
34 The file must contain a ^D at the beginning and at the end of the
35 file or the program will not be able to detect the end of file.
37 Lazyness/widgeting attack: This file does use the dialog manager
38 and uses mainly the dialog to achieve the help work. there is only
39 one specialized widget and it's only used to forward the mouse messages
40 to the appropiate routine.
48 #include <sys/types.h>
56 #include "key.h" /* For mi_getch() */
58 #include "dialog.h" /* For Dlg_head */
59 #include "widget.h" /* For Widget */
60 #include "wtools.h" /* For common_dialog_repaint() */
63 #define MAXLINKNAME 80
64 #define HISTORY_SIZE 20
65 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
67 #define STRING_LINK_START "\01"
68 #define STRING_LINK_POINTER "\02"
69 #define STRING_LINK_END "\03"
70 #define STRING_NODE_END "\04"
73 static int help_lines
; /* Lines in help viewer */
74 static int history_ptr
; /* For the history queue */
75 static const char *main_node
; /* The main node */
76 static const char *last_shown
= NULL
; /* Last byte shown in a screen */
77 static int end_of_node
= 0; /* Flag: the last character of the node shown? */
78 static const char *currentpoint
;
79 static const char *selected_item
;
81 /* The widget variables */
82 static Dlg_head
*whelp
;
85 const char *page
; /* Pointer to the selected page */
86 const char *link
; /* Pointer to the selected link */
87 } history
[HISTORY_SIZE
];
89 /* Link areas for the mouse */
90 typedef struct Link_Area
{
92 const char *link_name
;
93 struct Link_Area
*next
;
96 static Link_Area
*link_area
= NULL
;
97 static int inside_link_area
= 0;
99 static cb_ret_t
help_callback (struct Dlg_head
*h
, dlg_msg_t
, int parm
);
101 /* returns the position where text was found in the start buffer */
102 /* or 0 if not found */
104 search_string (const char *start
, const char *text
)
106 const char *result
= NULL
;
107 char *local_text
= g_strdup (text
);
108 char *d
= local_text
;
109 const char *e
= start
;
111 /* fmt sometimes replaces a space with a newline in the help file */
112 /* Replace the newlines in the link name with spaces to correct the situation */
119 for (d
= local_text
; *e
; e
++){
134 /* Searches text in the buffer pointed by start. Search ends */
135 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
136 static const char *search_string_node (const char *start
, const char *text
)
138 const char *d
= text
;
139 const char *e
= start
;
144 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
155 /* Searches the_char in the buffer pointer by start and searches */
156 /* it can search forward (direction = 1) or backward (direction = -1) */
157 static const char *search_char_node (const char *start
, char the_char
, int direction
)
163 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
170 /* Returns the new current pointer when moved lines lines */
171 static const char *move_forward2 (const char *c
, int lines
)
177 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
;
178 str_cnext_char (&p
)){
181 return currentpoint
= p
;
185 return currentpoint
= c
;
188 static const char *move_backward2 (const char *c
, int lines
)
194 for (line
= 0, p
= currentpoint
; *p
&& p
>= data
;
195 str_cprev_char (&p
)) {
197 if (*p
== CHAR_NODE_END
)
199 /* We reached the beginning of the node */
200 /* Skip the node headers */
201 while (*p
!= ']') str_cnext_char (&p
);
202 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
204 if (*(p
- 1) == '\n')
207 return currentpoint
= p
;
209 return currentpoint
= c
;
212 static void move_forward (int i
)
216 currentpoint
= move_forward2 (currentpoint
, i
);
219 static void move_backward (int i
)
221 currentpoint
= move_backward2 (currentpoint
, ++i
);
224 static void move_to_top (void)
226 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
228 while (*currentpoint
!= ']')
230 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
231 selected_item
= NULL
;
234 static void move_to_bottom (void)
236 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
239 move_backward (help_lines
- 1);
242 static const char *help_follow_link (const char *start
, const char *selected_item
)
244 char link_name
[MAXLINKNAME
];
251 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
253 if (*p
== CHAR_LINK_POINTER
){
255 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
256 link_name
[i
++] = *++p
;
257 link_name
[i
-1] = ']';
259 p
= search_string (data
, link_name
);
261 p
+= 1; /* Skip the newline following the start of the node */
266 /* Create a replacement page with the error message */
267 return _(" Help file format error\n");
270 static const char *select_next_link (const char *current_link
)
277 p
= search_string_node (current_link
, STRING_LINK_END
);
280 p
= search_string_node (p
, STRING_LINK_START
);
286 static const char *select_prev_link (const char *current_link
)
291 return search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
294 static void start_link_area (int x
, int y
, const char *link_name
)
298 if (inside_link_area
)
299 message (D_NORMAL
, _("Warning"), _(" Internal bug: Double start of link area "));
301 /* Allocate memory for a new link area */
302 new = g_new (Link_Area
, 1);
303 new->next
= link_area
;
306 /* Save the beginning coordinates of the link area */
310 /* Save the name of the destination anchor */
311 link_area
->link_name
= link_name
;
313 inside_link_area
= 1;
316 static void end_link_area (int x
, int y
)
318 if (inside_link_area
){
319 /* Save the end coordinates of the link area */
323 inside_link_area
= 0;
327 static void clear_link_areas (void)
333 link_area
= current
-> next
;
336 inside_link_area
= 0;
339 static void help_show (Dlg_head
*h
, const char *paint_start
)
344 int acs
; /* Flag: Alternate character set active? */
346 int active_col
, active_line
;/* Active link position */
347 static char buff
[MB_LEN_MAX
+ 1];
349 attrset (HELP_NORMAL_COLOR
);
352 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
355 if (selected_item
< paint_start
)
356 selected_item
= NULL
;
360 while (n
[0] != '\0' && n
[0] != CHAR_NODE_END
&& line
< help_lines
) {
362 n
= str_cget_next_char (p
);
363 memcpy (buff
, p
, n
- p
);
365 c
= (unsigned char) buff
[0];
368 case CHAR_LINK_START
:
369 if (selected_item
== NULL
)
371 if (p
== selected_item
){
372 attrset (HELP_SLINK_COLOR
);
374 /* Store the coordinates of the link */
375 active_col
= col
+ 2;
376 active_line
= line
+ 2;
379 attrset (HELP_LINK_COLOR
);
380 start_link_area (col
, line
, p
);
382 case CHAR_LINK_POINTER
:
384 end_link_area (col
- 1, line
);
388 attrset (HELP_NORMAL_COLOR
);
397 dlg_move (h
, line
+2, col
+2);
399 col
+= str_term_width1 (VERSION
);
402 attrset (HELP_BOLD_COLOR
);
404 case CHAR_FONT_ITALIC
:
405 attrset (HELP_ITALIC_COLOR
);
407 case CHAR_FONT_NORMAL
:
408 attrset (HELP_NORMAL_COLOR
);
415 col
= (col
/ 8 + 1) * 8;
420 w
= str_term_width1 (buff
);
421 if (col
+ w
> HELP_WINDOW_WIDTH
)
424 dlg_move (h
, line
+2, col
+2);
426 if (c
== ' ' || c
== '.')
432 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
442 end_of_node
= line
< help_lines
;
443 attrset (HELP_NORMAL_COLOR
);
444 if (selected_item
>= last_shown
){
445 if (link_area
!= NULL
){
446 selected_item
= link_area
->link_name
;
450 selected_item
= NULL
;
452 } while (repeat_paint
);
454 /* Position the cursor over a nice link */
456 dlg_move (h
, active_line
, active_col
);
460 help_event (Gpm_Event
*event
, void *vp
)
463 Link_Area
*current_area
;
465 if (! (event
->type
& GPM_UP
))
468 /* The event is relative to the dialog window, adjust it: */
472 if (event
->buttons
& GPM_B_RIGHT
){
473 currentpoint
= history
[history_ptr
].page
;
474 selected_item
= history
[history_ptr
].link
;
477 history_ptr
= HISTORY_SIZE
-1;
479 help_callback (w
->parent
, DLG_DRAW
, 0);
483 /* Test whether the mouse click is inside one of the link areas */
484 current_area
= link_area
;
487 /* Test one line link area */
488 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
489 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
491 /* Test two line link area */
492 if (current_area
->y1
+ 1 == current_area
->y2
){
494 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
496 /* The second line */
497 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
500 /* Mouse will not work with link areas of more than two lines */
502 current_area
= current_area
-> next
;
505 /* Test whether a link area was found */
507 /* The click was inside a link area -> follow the link */
508 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
509 history
[history_ptr
].page
= currentpoint
;
510 history
[history_ptr
].link
= current_area
->link_name
;
511 currentpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
512 selected_item
= NULL
;
515 move_backward (help_lines
- 1);
516 else if (event
->y
>= help_lines
)
517 move_forward (help_lines
- 1);
518 else if (event
->y
< help_lines
/2)
524 /* Show the new node */
525 help_callback (w
->parent
, DLG_DRAW
, 0);
532 help_help_cmd (void *vp
)
537 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
538 history
[history_ptr
].page
= currentpoint
;
539 history
[history_ptr
].link
= selected_item
;
541 p
= search_string(data
, "[How to use help]");
545 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
546 selected_item
= NULL
;
547 help_callback (h
, DLG_DRAW
, 0);
551 help_index_cmd (void *vp
)
554 const char *new_item
;
556 if (!(new_item
= search_string (data
, "[Contents]"))) {
557 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
562 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
563 history
[history_ptr
].page
= currentpoint
;
564 history
[history_ptr
].link
= selected_item
;
566 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
567 selected_item
= NULL
;
568 help_callback (h
, DLG_DRAW
, 0);
571 static void help_quit_cmd (void *vp
)
573 dlg_stop ((Dlg_head
*) vp
);
576 static void prev_node_cmd (void *vp
)
579 currentpoint
= history
[history_ptr
].page
;
580 selected_item
= history
[history_ptr
].link
;
583 history_ptr
= HISTORY_SIZE
-1;
585 help_callback (h
, DLG_DRAW
, 0);
589 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
592 return default_proc (msg
, parm
);
596 mousedispatch_new (int y
, int x
, int yl
, int xl
)
598 Widget
*w
= g_new (Widget
, 1);
600 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
605 static void help_cmk_move_backward(void *vp
, int lines
) {
607 move_backward(lines
);
609 static void help_cmk_move_forward(void *vp
, int lines
) {
613 static void help_cmk_moveto_top(void *vp
, int lines
) {
618 static void help_cmk_moveto_bottom(void *vp
, int lines
) {
625 help_handle_key (struct Dlg_head
*h
, int c
)
627 const char *new_item
;
629 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
630 check_movement_keys (c
, help_lines
, NULL
,
631 help_cmk_move_backward
,
632 help_cmk_move_forward
,
634 help_cmk_moveto_bottom
)) {
646 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
647 /* Is there any reason why the right key would take us
648 * backward if there are no links selected?, I agree
649 * with Torben than doing nothing in this case is better
651 /* If there are no links, go backward in history */
654 history_ptr
= HISTORY_SIZE
-1;
656 currentpoint
= history
[history_ptr
].page
;
657 selected_item
= history
[history_ptr
].link
;
660 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
661 history
[history_ptr
].page
= currentpoint
;
662 history
[history_ptr
].link
= selected_item
;
663 currentpoint
= help_follow_link (currentpoint
, selected_item
);
665 selected_item
= NULL
;
670 new_item
= select_next_link (selected_item
);
672 selected_item
= new_item
;
673 if (selected_item
>= last_shown
){
677 selected_item
= NULL
;
679 } else if (c
== KEY_DOWN
)
682 selected_item
= NULL
;
687 /* select previous link */
688 new_item
= select_prev_link (selected_item
);
689 selected_item
= new_item
;
690 if (selected_item
== NULL
|| selected_item
< currentpoint
) {
694 if (link_area
!= NULL
)
695 selected_item
= link_area
->link_name
;
697 selected_item
= NULL
;
704 new_item
= currentpoint
;
705 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
707 if (*++new_item
== '['){
708 while (*++new_item
) {
709 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
710 currentpoint
= new_item
;
711 selected_item
= NULL
;
720 new_item
= currentpoint
;
721 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
724 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
726 while (*new_item
!= ']')
728 currentpoint
= new_item
+ 2;
729 selected_item
= NULL
;
742 return MSG_NOT_HANDLED
;
745 help_callback (h
, DLG_DRAW
, 0);
750 help_callback (struct Dlg_head
*h
, dlg_msg_t msg
, int parm
)
754 common_dialog_repaint (h
);
755 help_show (h
, currentpoint
);
759 return help_handle_key (h
, parm
);
762 return default_dlg_callback (h
, msg
, parm
);
767 interactive_display_finish (void)
772 /* translate help file into terminal encoding */
774 translate_file (char *filedata
)
777 GString
*translated_data
;
779 translated_data
= g_string_new ("");
781 conv
= str_crt_conv_from ("UTF-8");
783 if (conv
!= INVALID_CONV
) {
784 if (str_convert (conv
, filedata
, translated_data
) != ESTR_FAILURE
) {
785 data
= translated_data
->str
;
789 str_close_conv (conv
);
791 g_string_free (translated_data
, TRUE
);
795 interactive_display (const char *filename
, const char *node
)
797 WButtonBar
*help_bar
;
799 char *hlpfile
= NULL
;
803 filedata
= load_file (filename
);
805 filedata
= load_mc_home_file ("mc.hlp", &hlpfile
);
808 message (D_ERROR
, MSG_ERROR
, _(" Cannot open file %s \n %s "), filename
? filename
: hlpfile
,
809 unix_error_string (errno
));
815 if (filedata
== NULL
)
818 translate_file (filedata
);
828 if (!(main_node
= search_string (data
, node
))) {
829 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
832 /* Fallback to [main], return if it also cannot be found */
833 main_node
= search_string (data
, "[main]");
835 interactive_display_finish ();
840 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
843 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
844 dialog_colors
, help_callback
, "[Help]", _("Help"),
845 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
847 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
848 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
850 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
852 history
[history_ptr
].page
= currentpoint
;
853 history
[history_ptr
].link
= selected_item
;
856 help_bar
= buttonbar_new (1);
857 ((Widget
*) help_bar
)->y
-= whelp
->y
;
858 ((Widget
*) help_bar
)->x
-= whelp
->x
;
860 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
862 add_widget (whelp
, md
);
863 add_widget (whelp
, help_bar
);
865 buttonbar_set_label_data (whelp
, 1, _("Help"), help_help_cmd
, whelp
);
866 buttonbar_set_label_data (whelp
, 2, _("Index"), help_index_cmd
, whelp
);
867 buttonbar_set_label_data (whelp
, 3, _("Prev"), prev_node_cmd
, whelp
);
868 buttonbar_clear_label (whelp
, 4);
869 buttonbar_clear_label (whelp
, 5);
870 buttonbar_clear_label (whelp
, 6);
871 buttonbar_clear_label (whelp
, 7);
872 buttonbar_clear_label (whelp
, 8);
873 buttonbar_clear_label (whelp
, 9);
874 buttonbar_set_label_data (whelp
, 10, _("Quit"), help_quit_cmd
, whelp
);
877 interactive_display_finish ();