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() */
62 #define MAXLINKNAME 80
63 #define HISTORY_SIZE 20
64 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
66 #define STRING_LINK_START "\01"
67 #define STRING_LINK_POINTER "\02"
68 #define STRING_LINK_END "\03"
69 #define STRING_NODE_END "\04"
71 static char *data
; /* Pointer to the loaded data file */
72 static int help_lines
; /* Lines in help viewer */
73 static int history_ptr
; /* For the history queue */
74 static const char *main_node
; /* The main node */
75 static const char *last_shown
= NULL
; /* Last byte shown in a screen */
76 static int end_of_node
= 0; /* Flag: the last character of the node shown? */
77 static const char *currentpoint
;
78 static const char *selected_item
;
80 /* The widget variables */
81 static Dlg_head
*whelp
;
84 const char *page
; /* Pointer to the selected page */
85 const char *link
; /* Pointer to the selected link */
86 } history
[HISTORY_SIZE
];
88 /* Link areas for the mouse */
89 typedef struct Link_Area
{
91 const char *link_name
;
92 struct Link_Area
*next
;
95 static Link_Area
*link_area
= NULL
;
96 static int inside_link_area
= 0;
98 static cb_ret_t
help_callback (struct Dlg_head
*h
, dlg_msg_t
, int parm
);
100 /* returns the position where text was found in the start buffer */
101 /* or 0 if not found */
103 search_string (const char *start
, const char *text
)
105 const char *result
= NULL
;
106 char *local_text
= g_strdup (text
);
107 char *d
= local_text
;
108 const char *e
= start
;
110 /* fmt sometimes replaces a space with a newline in the help file */
111 /* Replace the newlines in the link name with spaces to correct the situation */
118 for (d
= local_text
; *e
; e
++){
133 /* Searches text in the buffer pointed by start. Search ends */
134 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
135 static const char *search_string_node (const char *start
, const char *text
)
137 const char *d
= text
;
138 const char *e
= start
;
143 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
154 /* Searches the_char in the buffer pointer by start and searches */
155 /* it can search forward (direction = 1) or backward (direction = -1) */
156 static const char *search_char_node (const char *start
, char the_char
, int direction
)
162 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
169 /* Returns the new current pointer when moved lines lines */
170 static const char *move_forward2 (const char *c
, int lines
)
176 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
; p
++){
178 return currentpoint
= p
;
182 return currentpoint
= c
;
185 static const char *move_backward2 (const char *c
, int lines
)
191 for (line
= 0, p
= currentpoint
; *p
&& p
>= data
; p
--){
192 if (*p
== CHAR_NODE_END
)
194 /* We reached the beginning of the node */
195 /* Skip the node headers */
196 while (*p
!= ']') p
++;
197 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
199 if (*(p
- 1) == '\n')
202 return currentpoint
= p
;
204 return currentpoint
= c
;
207 static void move_forward (int i
)
211 currentpoint
= move_forward2 (currentpoint
, i
);
214 static void move_backward (int i
)
216 currentpoint
= move_backward2 (currentpoint
, ++i
);
219 static void move_to_top (void)
221 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
223 while (*currentpoint
!= ']')
225 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
226 selected_item
= NULL
;
229 static void move_to_bottom (void)
231 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
234 move_backward (help_lines
- 1);
237 static const char *help_follow_link (const char *start
, const char *selected_item
)
239 char link_name
[MAXLINKNAME
];
246 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
248 if (*p
== CHAR_LINK_POINTER
){
250 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
251 link_name
[i
++] = *++p
;
252 link_name
[i
-1] = ']';
254 p
= search_string (data
, link_name
);
256 p
+= 1; /* Skip the newline following the start of the node */
261 /* Create a replacement page with the error message */
262 return _(" Help file format error\n");
265 static const char *select_next_link (const char *current_link
)
272 p
= search_string_node (current_link
, STRING_LINK_END
);
275 p
= search_string_node (p
, STRING_LINK_START
);
281 static const char *select_prev_link (const char *current_link
)
286 return search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
289 static void start_link_area (int x
, int y
, const char *link_name
)
293 if (inside_link_area
)
294 message (D_NORMAL
, _("Warning"), _(" Internal bug: Double start of link area "));
296 /* Allocate memory for a new link area */
297 new = g_new (Link_Area
, 1);
298 new->next
= link_area
;
301 /* Save the beginning coordinates of the link area */
305 /* Save the name of the destination anchor */
306 link_area
->link_name
= link_name
;
308 inside_link_area
= 1;
311 static void end_link_area (int x
, int y
)
313 if (inside_link_area
){
314 /* Save the end coordinates of the link area */
318 inside_link_area
= 0;
322 static void clear_link_areas (void)
328 link_area
= current
-> next
;
331 inside_link_area
= 0;
334 static void help_show (Dlg_head
*h
, const char *paint_start
)
339 int acs
; /* Flag: Alternate character set active? */
341 int active_col
, active_line
;/* Active link position */
343 attrset (HELP_NORMAL_COLOR
);
346 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
349 if (selected_item
< paint_start
)
350 selected_item
= NULL
;
352 for (p
= paint_start
; *p
&& *p
!= CHAR_NODE_END
&& line
< help_lines
; p
++) {
353 c
= (unsigned char)*p
;
355 case CHAR_LINK_START
:
356 if (selected_item
== NULL
)
358 if (p
== selected_item
){
359 attrset (HELP_SLINK_COLOR
);
361 /* Store the coordinates of the link */
362 active_col
= col
+ 2;
363 active_line
= line
+ 2;
366 attrset (HELP_LINK_COLOR
);
367 start_link_area (col
, line
, p
);
369 case CHAR_LINK_POINTER
:
371 end_link_area (col
- 1, line
);
375 attrset (HELP_NORMAL_COLOR
);
384 dlg_move (h
, line
+2, col
+2);
386 col
+= strlen (VERSION
);
389 attrset (HELP_BOLD_COLOR
);
391 case CHAR_FONT_ITALIC
:
392 attrset (HELP_ITALIC_COLOR
);
394 case CHAR_FONT_NORMAL
:
395 attrset (HELP_NORMAL_COLOR
);
402 col
= (col
/8 + 1) * 8;
407 if (col
> HELP_WINDOW_WIDTH
-1)
410 dlg_move (h
, line
+2, col
+2);
412 if (c
== ' ' || c
== '.')
418 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
427 end_of_node
= line
< help_lines
;
428 attrset (HELP_NORMAL_COLOR
);
429 if (selected_item
>= last_shown
){
430 if (link_area
!= NULL
){
431 selected_item
= link_area
->link_name
;
435 selected_item
= NULL
;
437 } while (repeat_paint
);
439 /* Position the cursor over a nice link */
441 dlg_move (h
, active_line
, active_col
);
445 help_event (Gpm_Event
*event
, void *vp
)
448 Link_Area
*current_area
;
450 if (! (event
->type
& GPM_UP
))
453 /* The event is relative to the dialog window, adjust it: */
457 if (event
->buttons
& GPM_B_RIGHT
){
458 currentpoint
= history
[history_ptr
].page
;
459 selected_item
= history
[history_ptr
].link
;
462 history_ptr
= HISTORY_SIZE
-1;
464 help_callback (w
->parent
, DLG_DRAW
, 0);
468 /* Test whether the mouse click is inside one of the link areas */
469 current_area
= link_area
;
472 /* Test one line link area */
473 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
474 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
476 /* Test two line link area */
477 if (current_area
->y1
+ 1 == current_area
->y2
){
479 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
481 /* The second line */
482 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
485 /* Mouse will not work with link areas of more than two lines */
487 current_area
= current_area
-> next
;
490 /* Test whether a link area was found */
492 /* The click was inside a link area -> follow the link */
493 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
494 history
[history_ptr
].page
= currentpoint
;
495 history
[history_ptr
].link
= current_area
->link_name
;
496 currentpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
497 selected_item
= NULL
;
500 move_backward (help_lines
- 1);
501 else if (event
->y
>= help_lines
)
502 move_forward (help_lines
- 1);
503 else if (event
->y
< help_lines
/2)
509 /* Show the new node */
510 help_callback (w
->parent
, DLG_DRAW
, 0);
517 help_help_cmd (void *vp
)
522 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
523 history
[history_ptr
].page
= currentpoint
;
524 history
[history_ptr
].link
= selected_item
;
526 p
= search_string(data
, "[How to use help]");
530 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
531 selected_item
= NULL
;
532 help_callback (h
, DLG_DRAW
, 0);
536 help_index_cmd (void *vp
)
539 const char *new_item
;
541 if (!(new_item
= search_string (data
, "[Contents]"))) {
542 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
547 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
548 history
[history_ptr
].page
= currentpoint
;
549 history
[history_ptr
].link
= selected_item
;
551 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
552 selected_item
= NULL
;
553 help_callback (h
, DLG_DRAW
, 0);
556 static void help_quit_cmd (void *vp
)
558 dlg_stop ((Dlg_head
*) vp
);
561 static void prev_node_cmd (void *vp
)
564 currentpoint
= history
[history_ptr
].page
;
565 selected_item
= history
[history_ptr
].link
;
568 history_ptr
= HISTORY_SIZE
-1;
570 help_callback (h
, DLG_DRAW
, 0);
574 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
577 return default_proc (msg
, parm
);
581 mousedispatch_new (int y
, int x
, int yl
, int xl
)
583 Widget
*w
= g_new (Widget
, 1);
585 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
590 static void help_cmk_move_backward(void *vp
, int lines
) {
592 move_backward(lines
);
594 static void help_cmk_move_forward(void *vp
, int lines
) {
598 static void help_cmk_moveto_top(void *vp
, int lines
) {
603 static void help_cmk_moveto_bottom(void *vp
, int lines
) {
610 help_handle_key (struct Dlg_head
*h
, int c
)
612 const char *new_item
;
614 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
615 check_movement_keys (c
, help_lines
, NULL
,
616 help_cmk_move_backward
,
617 help_cmk_move_forward
,
619 help_cmk_moveto_bottom
)) {
631 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
632 /* Is there any reason why the right key would take us
633 * backward if there are no links selected?, I agree
634 * with Torben than doing nothing in this case is better
636 /* If there are no links, go backward in history */
639 history_ptr
= HISTORY_SIZE
-1;
641 currentpoint
= history
[history_ptr
].page
;
642 selected_item
= history
[history_ptr
].link
;
645 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
646 history
[history_ptr
].page
= currentpoint
;
647 history
[history_ptr
].link
= selected_item
;
648 currentpoint
= help_follow_link (currentpoint
, selected_item
);
650 selected_item
= NULL
;
655 new_item
= select_next_link (selected_item
);
657 selected_item
= new_item
;
658 if (selected_item
>= last_shown
){
662 selected_item
= NULL
;
664 } else if (c
== KEY_DOWN
)
667 selected_item
= NULL
;
672 /* select previous link */
673 new_item
= select_prev_link (selected_item
);
674 selected_item
= new_item
;
675 if (selected_item
== NULL
|| selected_item
< currentpoint
) {
679 if (link_area
!= NULL
)
680 selected_item
= link_area
->link_name
;
682 selected_item
= NULL
;
689 new_item
= currentpoint
;
690 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
692 if (*++new_item
== '['){
693 while (*++new_item
) {
694 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
695 currentpoint
= new_item
;
696 selected_item
= NULL
;
705 new_item
= currentpoint
;
706 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
709 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
711 while (*new_item
!= ']')
713 currentpoint
= new_item
+ 2;
714 selected_item
= NULL
;
727 return MSG_NOT_HANDLED
;
730 help_callback (h
, DLG_DRAW
, 0);
735 help_callback (struct Dlg_head
*h
, dlg_msg_t msg
, int parm
)
739 common_dialog_repaint (h
);
740 help_show (h
, currentpoint
);
744 return help_handle_key (h
, parm
);
747 return default_dlg_callback (h
, msg
, parm
);
752 interactive_display_finish (void)
759 interactive_display (const char *filename
, const char *node
)
761 WButtonBar
*help_bar
;
763 char *hlpfile
= NULL
;
766 data
= load_file (filename
);
768 data
= load_mc_home_file ("mc.hlp", &hlpfile
);
771 message (D_ERROR
, MSG_ERROR
, _(" Cannot open file %s \n %s "), filename
? filename
: hlpfile
,
772 unix_error_string (errno
));
784 if (!(main_node
= search_string (data
, node
))) {
785 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
788 /* Fallback to [main], return if it also cannot be found */
789 main_node
= search_string (data
, "[main]");
791 interactive_display_finish ();
796 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
799 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
800 dialog_colors
, help_callback
, "[Help]", _("Help"),
801 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
803 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
804 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
806 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
808 history
[history_ptr
].page
= currentpoint
;
809 history
[history_ptr
].link
= selected_item
;
812 help_bar
= buttonbar_new (1);
813 ((Widget
*) help_bar
)->y
-= whelp
->y
;
814 ((Widget
*) help_bar
)->x
-= whelp
->x
;
816 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
818 add_widget (whelp
, md
);
819 add_widget (whelp
, help_bar
);
821 buttonbar_set_label_data (whelp
, 1, _("Help"), help_help_cmd
, whelp
);
822 buttonbar_set_label_data (whelp
, 2, _("Index"), help_index_cmd
, whelp
);
823 buttonbar_set_label_data (whelp
, 3, _("Prev"), prev_node_cmd
, whelp
);
824 buttonbar_clear_label (whelp
, 4);
825 buttonbar_clear_label (whelp
, 5);
826 buttonbar_clear_label (whelp
, 6);
827 buttonbar_clear_label (whelp
, 7);
828 buttonbar_clear_label (whelp
, 8);
829 buttonbar_clear_label (whelp
, 9);
830 buttonbar_set_label_data (whelp
, 10, _("Quit"), help_quit_cmd
, whelp
);
833 interactive_display_finish ();