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.
49 #include <sys/types.h>
57 #include "key.h" /* For mi_getch() */
59 #include "dialog.h" /* For Dlg_head */
60 #include "widget.h" /* For Widget */
61 #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"
72 static char *data
; /* Pointer to the loaded data file */
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
; p
++){
179 return currentpoint
= p
;
183 return currentpoint
= c
;
186 static const char *move_backward2 (const char *c
, int lines
)
192 for (line
= 0, p
= currentpoint
; *p
&& p
>= data
; p
--){
193 if (*p
== CHAR_NODE_END
)
195 /* We reached the beginning of the node */
196 /* Skip the node headers */
197 while (*p
!= ']') p
++;
198 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
200 if (*(p
- 1) == '\n')
203 return currentpoint
= p
;
205 return currentpoint
= c
;
208 static void move_forward (int i
)
212 currentpoint
= move_forward2 (currentpoint
, i
);
215 static void move_backward (int i
)
217 currentpoint
= move_backward2 (currentpoint
, ++i
);
220 static void move_to_top (void)
222 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
224 while (*currentpoint
!= ']')
226 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
227 selected_item
= NULL
;
230 static void move_to_bottom (void)
232 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
235 move_backward (help_lines
- 1);
238 static const char *help_follow_link (const char *start
, const char *selected_item
)
240 char link_name
[MAXLINKNAME
];
247 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
249 if (*p
== CHAR_LINK_POINTER
){
251 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
252 link_name
[i
++] = *++p
;
253 link_name
[i
-1] = ']';
255 p
= search_string (data
, link_name
);
257 p
+= 1; /* Skip the newline following the start of the node */
262 /* Create a replacement page with the error message */
263 return _(" Help file format error\n");
266 static const char *select_next_link (const char *current_link
)
273 p
= search_string_node (current_link
, STRING_LINK_END
);
276 p
= search_string_node (p
, STRING_LINK_START
);
282 static const char *select_prev_link (const char *current_link
)
287 return search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
290 static void start_link_area (int x
, int y
, const char *link_name
)
294 if (inside_link_area
)
295 message (0, _("Warning"), _(" Internal bug: Double start of link area "));
297 /* Allocate memory for a new link area */
298 new = g_new (Link_Area
, 1);
299 new->next
= link_area
;
302 /* Save the beginning coordinates of the link area */
306 /* Save the name of the destination anchor */
307 link_area
->link_name
= link_name
;
309 inside_link_area
= 1;
312 static void end_link_area (int x
, int y
)
314 if (inside_link_area
){
315 /* Save the end coordinates of the link area */
319 inside_link_area
= 0;
323 static void clear_link_areas (void)
329 link_area
= current
-> next
;
332 inside_link_area
= 0;
335 static void help_show (Dlg_head
*h
, const char *paint_start
)
340 int acs
; /* Flag: Alternate character set active? */
342 int active_col
, active_line
;/* Active link position */
344 attrset (HELP_NORMAL_COLOR
);
347 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
350 if (selected_item
< paint_start
)
351 selected_item
= NULL
;
353 for (p
= paint_start
; *p
&& *p
!= CHAR_NODE_END
&& line
< help_lines
; p
++) {
354 c
= (unsigned char)*p
;
356 case CHAR_LINK_START
:
357 if (selected_item
== NULL
)
359 if (p
== selected_item
){
360 attrset (HELP_SLINK_COLOR
);
362 /* Store the coordinates of the link */
363 active_col
= col
+ 2;
364 active_line
= line
+ 2;
367 attrset (HELP_LINK_COLOR
);
368 start_link_area (col
, line
, p
);
370 case CHAR_LINK_POINTER
:
372 end_link_area (col
- 1, line
);
376 attrset (HELP_NORMAL_COLOR
);
385 dlg_move (h
, line
+2, col
+2);
387 col
+= strlen (VERSION
);
390 attrset (HELP_BOLD_COLOR
);
392 case CHAR_FONT_ITALIC
:
393 attrset (HELP_ITALIC_COLOR
);
395 case CHAR_FONT_NORMAL
:
396 attrset (HELP_NORMAL_COLOR
);
403 col
= (col
/8 + 1) * 8;
408 if (col
> HELP_WINDOW_WIDTH
-1)
411 dlg_move (h
, line
+2, col
+2);
413 if (c
== ' ' || c
== '.')
419 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
428 end_of_node
= line
< help_lines
;
429 attrset (HELP_NORMAL_COLOR
);
430 if (selected_item
>= last_shown
){
431 if (link_area
!= NULL
){
432 selected_item
= link_area
->link_name
;
436 selected_item
= NULL
;
438 } while (repeat_paint
);
440 /* Position the cursor over a nice link */
442 dlg_move (h
, active_line
, active_col
);
446 help_event (Gpm_Event
*event
, void *vp
)
449 Link_Area
*current_area
;
451 if (! (event
->type
& GPM_UP
))
454 /* The event is relative to the dialog window, adjust it: */
458 if (event
->buttons
& GPM_B_RIGHT
){
459 currentpoint
= history
[history_ptr
].page
;
460 selected_item
= history
[history_ptr
].link
;
463 history_ptr
= HISTORY_SIZE
-1;
465 help_callback (w
->parent
, DLG_DRAW
, 0);
469 /* Test whether the mouse click is inside one of the link areas */
470 current_area
= link_area
;
473 /* Test one line link area */
474 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
475 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
477 /* Test two line link area */
478 if (current_area
->y1
+ 1 == current_area
->y2
){
480 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
482 /* The second line */
483 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
486 /* Mouse will not work with link areas of more than two lines */
488 current_area
= current_area
-> next
;
491 /* Test whether a link area was found */
493 /* The click was inside a link area -> follow the link */
494 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
495 history
[history_ptr
].page
= currentpoint
;
496 history
[history_ptr
].link
= current_area
->link_name
;
497 currentpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
498 selected_item
= NULL
;
501 move_backward (help_lines
- 1);
502 else if (event
->y
>= help_lines
)
503 move_forward (help_lines
- 1);
504 else if (event
->y
< help_lines
/2)
510 /* Show the new node */
511 help_callback (w
->parent
, DLG_DRAW
, 0);
518 help_help_cmd (void *vp
)
523 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
524 history
[history_ptr
].page
= currentpoint
;
525 history
[history_ptr
].link
= selected_item
;
527 p
= search_string(data
, "[How to use help]");
531 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
532 selected_item
= NULL
;
533 help_callback (h
, DLG_DRAW
, 0);
537 help_index_cmd (void *vp
)
540 const char *new_item
;
542 if (!(new_item
= search_string (data
, "[Contents]"))) {
543 message (1, MSG_ERROR
, _(" Cannot find node %s in help file "),
548 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
549 history
[history_ptr
].page
= currentpoint
;
550 history
[history_ptr
].link
= selected_item
;
552 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
553 selected_item
= NULL
;
554 help_callback (h
, DLG_DRAW
, 0);
557 static void help_quit_cmd (void *vp
)
559 dlg_stop ((Dlg_head
*) vp
);
562 static void prev_node_cmd (void *vp
)
565 currentpoint
= history
[history_ptr
].page
;
566 selected_item
= history
[history_ptr
].link
;
569 history_ptr
= HISTORY_SIZE
-1;
571 help_callback (h
, DLG_DRAW
, 0);
575 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
578 return default_proc (msg
, parm
);
582 mousedispatch_new (int y
, int x
, int yl
, int xl
)
584 Widget
*w
= g_new (Widget
, 1);
586 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
591 static void help_cmk_move_backward(void *vp
, int lines
) {
593 move_backward(lines
);
595 static void help_cmk_move_forward(void *vp
, int lines
) {
599 static void help_cmk_moveto_top(void *vp
, int lines
) {
604 static void help_cmk_moveto_bottom(void *vp
, int lines
) {
611 help_handle_key (struct Dlg_head
*h
, int c
)
613 const char *new_item
;
615 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
616 check_movement_keys (c
, help_lines
, NULL
,
617 help_cmk_move_backward
,
618 help_cmk_move_forward
,
620 help_cmk_moveto_bottom
)) {
632 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
633 /* Is there any reason why the right key would take us
634 * backward if there are no links selected?, I agree
635 * with Torben than doing nothing in this case is better
637 /* If there are no links, go backward in history */
640 history_ptr
= HISTORY_SIZE
-1;
642 currentpoint
= history
[history_ptr
].page
;
643 selected_item
= history
[history_ptr
].link
;
646 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
647 history
[history_ptr
].page
= currentpoint
;
648 history
[history_ptr
].link
= selected_item
;
649 currentpoint
= help_follow_link (currentpoint
, selected_item
);
651 selected_item
= NULL
;
656 new_item
= select_next_link (selected_item
);
658 selected_item
= new_item
;
659 if (selected_item
>= last_shown
){
663 selected_item
= NULL
;
665 } else if (c
== KEY_DOWN
)
668 selected_item
= NULL
;
673 /* select previous link */
674 new_item
= select_prev_link (selected_item
);
675 selected_item
= new_item
;
676 if (selected_item
== NULL
|| selected_item
< currentpoint
) {
680 if (link_area
!= NULL
)
681 selected_item
= link_area
->link_name
;
683 selected_item
= NULL
;
690 new_item
= currentpoint
;
691 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
693 if (*++new_item
== '['){
694 while (*++new_item
) {
695 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
696 currentpoint
= new_item
;
697 selected_item
= NULL
;
706 new_item
= currentpoint
;
707 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
710 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
712 while (*new_item
!= ']')
714 currentpoint
= new_item
+ 2;
715 selected_item
= NULL
;
728 return MSG_NOT_HANDLED
;
731 help_callback (h
, DLG_DRAW
, 0);
736 help_callback (struct Dlg_head
*h
, dlg_msg_t msg
, int parm
)
740 common_dialog_repaint (h
);
741 help_show (h
, currentpoint
);
745 return help_handle_key (h
, parm
);
748 return default_dlg_callback (h
, msg
, parm
);
753 interactive_display_finish (void)
760 interactive_display (const char *filename
, const char *node
)
762 WButtonBar
*help_bar
;
764 char *hlpfile
= NULL
;
767 data
= load_file (filename
);
769 data
= load_mc_home_file ("mc.hlp", &hlpfile
);
772 message (1, MSG_ERROR
, _(" Cannot open file %s \n %s "), filename
? filename
: hlpfile
,
773 unix_error_string (errno
));
785 if (!(main_node
= search_string (data
, node
))) {
786 message (1, MSG_ERROR
, _(" Cannot find node %s in help file "),
789 /* Fallback to [main], return if it also cannot be found */
790 main_node
= search_string (data
, "[main]");
792 interactive_display_finish ();
797 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
800 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
801 dialog_colors
, help_callback
, "[Help]", _("Help"),
802 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
804 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
805 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
807 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
809 history
[history_ptr
].page
= currentpoint
;
810 history
[history_ptr
].link
= selected_item
;
813 help_bar
= buttonbar_new (1);
814 ((Widget
*) help_bar
)->y
-= whelp
->y
;
815 ((Widget
*) help_bar
)->x
-= whelp
->x
;
817 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
819 add_widget (whelp
, md
);
820 add_widget (whelp
, help_bar
);
822 buttonbar_set_label_data (whelp
, 1, _("Help"), help_help_cmd
, whelp
);
823 buttonbar_set_label_data (whelp
, 2, _("Index"), help_index_cmd
, whelp
);
824 buttonbar_set_label_data (whelp
, 3, _("Prev"), prev_node_cmd
, whelp
);
825 buttonbar_clear_label (whelp
, 4);
826 buttonbar_clear_label (whelp
, 5);
827 buttonbar_clear_label (whelp
, 6);
828 buttonbar_clear_label (whelp
, 7);
829 buttonbar_clear_label (whelp
, 8);
830 buttonbar_clear_label (whelp
, 9);
831 buttonbar_set_label_data (whelp
, 10, _("Quit"), help_quit_cmd
, whelp
);
834 interactive_display_finish ();