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"
62 #include "dialog.h" /* For Dlg_head */
63 #include "widget.h" /* For Widget */
64 #include "wtools.h" /* For common_dialog_repaint() */
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
;
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
{
96 const char *link_name
;
97 struct Link_Area
*next
;
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 */
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 */
123 for (d
= local_text
; *e
; e
++){
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
;
148 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
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
)
167 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
174 /* Returns the new current pointer when moved lines lines */
175 static const char *move_forward2 (const char *c
, int lines
)
181 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
;
182 str_cnext_char (&p
)){
185 return currentpoint
= p
;
189 return currentpoint
= c
;
192 static const char *move_backward2 (const char *c
, int lines
)
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')
211 return currentpoint
= p
;
213 return currentpoint
= c
;
216 static void move_forward (int i
)
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
)
232 while (*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
)
243 move_backward (help_lines
- 1);
246 static const char *help_follow_link (const char *start
, const char *lc_selected_item
)
248 char link_name
[MAXLINKNAME
];
252 if (!lc_selected_item
)
255 for (p
= lc_selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
257 if (*p
== CHAR_LINK_POINTER
){
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] = ']';
263 p
= search_string (data
, link_name
);
265 p
+= 1; /* Skip the newline following the start of the node */
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
)
281 p
= search_string_node (current_link
, STRING_LINK_END
);
284 p
= search_string_node (p
, STRING_LINK_START
);
290 static const char *select_prev_link (const char *current_link
)
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
)
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
;
310 /* Save the beginning coordinates of the link area */
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 */
327 inside_link_area
= 0;
331 static void clear_link_areas (void)
337 link_area
= current
-> next
;
340 inside_link_area
= 0;
343 static void help_show (Dlg_head
*h
, const char *paint_start
)
348 int acs
; /* Flag: Alternate character set active? */
350 int active_col
, active_line
;/* Active link position */
351 static char buff
[MB_LEN_MAX
+ 1];
353 tty_setcolor (HELP_NORMAL_COLOR
);
356 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
359 if (selected_item
< paint_start
)
360 selected_item
= NULL
;
364 while (n
[0] != '\0' && n
[0] != CHAR_NODE_END
&& line
< help_lines
) {
366 n
= str_cget_next_char (p
);
367 memcpy (buff
, p
, n
- p
);
369 c
= (unsigned char) buff
[0];
372 case CHAR_LINK_START
:
373 if (selected_item
== NULL
)
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;
383 tty_setcolor (HELP_LINK_COLOR
);
384 start_link_area (col
, line
, p
);
386 case CHAR_LINK_POINTER
:
388 end_link_area (col
- 1, line
);
392 tty_setcolor (HELP_NORMAL_COLOR
);
401 dlg_move (h
, line
+2, col
+2);
402 tty_print_string (VERSION
);
403 col
+= str_term_width1 (VERSION
);
406 tty_setcolor (HELP_BOLD_COLOR
);
408 case CHAR_FONT_ITALIC
:
409 tty_setcolor (HELP_ITALIC_COLOR
);
411 case CHAR_FONT_NORMAL
:
412 tty_setcolor (HELP_NORMAL_COLOR
);
419 col
= (col
/ 8 + 1) * 8;
424 w
= str_term_width1 (buff
);
425 if (col
+ w
> HELP_WINDOW_WIDTH
)
428 dlg_move (h
, line
+2, col
+2);
430 if (c
== ' ' || c
== '.')
434 tty_print_char (acs_map
[c
]);
436 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
439 tty_print_string (buff
);
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
;
454 selected_item
= NULL
;
456 } while (repeat_paint
);
458 /* Position the cursor over a nice link */
460 dlg_move (h
, active_line
, active_col
);
464 help_event (Gpm_Event
*event
, void *vp
)
467 Link_Area
*current_area
;
469 if (! (event
->type
& GPM_UP
))
472 /* The event is relative to the dialog window, adjust it: */
476 if (event
->buttons
& GPM_B_RIGHT
){
477 currentpoint
= history
[history_ptr
].page
;
478 selected_item
= history
[history_ptr
].link
;
481 history_ptr
= HISTORY_SIZE
-1;
483 help_callback (w
->parent
, DLG_DRAW
, 0);
487 /* Test whether the mouse click is inside one of the link areas */
488 current_area
= link_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
)
495 /* Test two line link area */
496 if (current_area
->y1
+ 1 == current_area
->y2
){
498 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
500 /* The second line */
501 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
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 */
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
;
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)
528 /* Show the new node */
529 help_callback (w
->parent
, DLG_DRAW
, 0);
536 help_help_cmd (void *vp
)
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]");
549 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
550 selected_item
= NULL
;
551 help_callback (h
, DLG_DRAW
, 0);
555 help_index_cmd (void *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 "),
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
)
583 currentpoint
= history
[history_ptr
].page
;
584 selected_item
= history
[history_ptr
].link
;
587 history_ptr
= HISTORY_SIZE
-1;
589 help_callback (h
, DLG_DRAW
, 0);
593 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
597 w
->lines
= help_lines
;
601 return default_proc (msg
, parm
);
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
);
615 static void help_cmk_move_backward(void *vp
, int lines
) {
617 move_backward(lines
);
619 static void help_cmk_move_forward(void *vp
, int lines
) {
623 static void help_cmk_moveto_top(void *vp
, int lines
) {
628 static void help_cmk_moveto_bottom(void *vp
, int lines
) {
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
,
644 help_cmk_moveto_bottom
)) {
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 */
664 history_ptr
= HISTORY_SIZE
-1;
666 currentpoint
= history
[history_ptr
].page
;
667 selected_item
= history
[history_ptr
].link
;
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
;
680 new_item
= select_next_link (selected_item
);
682 selected_item
= new_item
;
683 if (selected_item
>= last_shown
){
687 selected_item
= NULL
;
689 } else if (c
== KEY_DOWN
)
692 selected_item
= NULL
;
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
) {
704 if (link_area
!= NULL
)
705 selected_item
= link_area
->link_name
;
707 selected_item
= NULL
;
714 new_item
= currentpoint
;
715 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
717 if (*++new_item
== '['){
718 while (*++new_item
) {
719 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
720 currentpoint
= new_item
;
721 selected_item
= NULL
;
730 new_item
= currentpoint
;
731 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
734 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
736 while (*new_item
!= ']')
738 currentpoint
= new_item
+ 2;
739 selected_item
= NULL
;
752 return MSG_NOT_HANDLED
;
754 help_callback (h
, DLG_DRAW
, 0);
759 help_callback (Dlg_head
*h
, dlg_msg_t msg
, int parm
)
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
);
772 common_dialog_repaint (h
);
773 help_show (h
, currentpoint
);
777 return help_handle_key (h
, parm
);
780 return default_dlg_callback (h
, msg
, parm
);
785 interactive_display_finish (void)
790 /* translate help file into terminal encoding */
792 translate_file (char *filedata
)
795 GString
*translated_data
;
797 translated_data
= g_string_new ("");
799 conv
= str_crt_conv_from ("UTF-8");
801 if (conv
!= INVALID_CONV
) {
804 if (str_convert (conv
, filedata
, translated_data
) != ESTR_FAILURE
) {
805 data
= translated_data
->str
;
806 g_string_free (translated_data
, FALSE
);
809 g_string_free (translated_data
, TRUE
);
811 str_close_conv (conv
);
813 g_string_free (translated_data
, TRUE
);
817 interactive_display (const char *filename
, const char *node
)
819 WButtonBar
*help_bar
;
821 char *hlpfile
= NULL
;
825 filedata
= load_file (filename
);
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
));
837 if (filedata
== NULL
)
840 translate_file (filedata
);
850 if (!(main_node
= search_string (data
, node
))) {
851 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
854 /* Fallback to [main], return if it also cannot be found */
855 main_node
= search_string (data
, "[main]");
857 interactive_display_finish ();
862 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
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
;) {
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, Q_("ButtonBar|Help"), help_help_cmd
, whelp
);
888 buttonbar_set_label_data (whelp
, 2, Q_("ButtonBar|Index"), help_index_cmd
, whelp
);
889 buttonbar_set_label_data (whelp
, 3, Q_("ButtonBar|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, Q_("ButtonBar|Quit"), help_quit_cmd
, whelp
);
899 interactive_display_finish ();