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>
51 #include <mhl/memory.h>
52 #include <mhl/string.h>
59 #include "key.h" /* For mi_getch() */
61 #include "dialog.h" /* For Dlg_head */
62 #include "widget.h" /* For Widget */
63 #include "wtools.h" /* For common_dialog_repaint() */
65 #define MAXLINKNAME 80
66 #define HISTORY_SIZE 20
67 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
69 #define STRING_LINK_START "\01"
70 #define STRING_LINK_POINTER "\02"
71 #define STRING_LINK_END "\03"
72 #define STRING_NODE_END "\04"
74 static char *data
; /* Pointer to the loaded data file */
75 static int help_lines
; /* Lines in help viewer */
76 static int history_ptr
; /* For the history queue */
77 static const char *main_node
; /* The main node */
78 static const char *last_shown
= NULL
; /* Last byte shown in a screen */
79 static int end_of_node
= 0; /* Flag: the last character of the node shown? */
80 static const char *currentpoint
;
81 static const char *selected_item
;
83 /* The widget variables */
84 static Dlg_head
*whelp
;
87 const char *page
; /* Pointer to the selected page */
88 const char *link
; /* Pointer to the selected link */
89 } history
[HISTORY_SIZE
];
91 /* Link areas for the mouse */
92 typedef struct Link_Area
{
94 const char *link_name
;
95 struct Link_Area
*next
;
98 static Link_Area
*link_area
= NULL
;
99 static int inside_link_area
= 0;
101 static cb_ret_t
help_callback (struct Dlg_head
*h
, dlg_msg_t
, int parm
);
103 /* returns the position where text was found in the start buffer */
104 /* or 0 if not found */
106 search_string (const char *start
, const char *text
)
108 const char *result
= NULL
;
109 char *local_text
= g_strdup (text
);
110 char *d
= local_text
;
111 const char *e
= start
;
113 /* fmt sometimes replaces a space with a newline in the help file */
114 /* Replace the newlines in the link name with spaces to correct the situation */
121 for (d
= local_text
; *e
; e
++){
136 /* Searches text in the buffer pointed by start. Search ends */
137 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
138 static const char *search_string_node (const char *start
, const char *text
)
140 const char *d
= text
;
141 const char *e
= start
;
146 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
157 /* Searches the_char in the buffer pointer by start and searches */
158 /* it can search forward (direction = 1) or backward (direction = -1) */
159 static const char *search_char_node (const char *start
, char the_char
, int direction
)
165 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
172 /* Returns the new current pointer when moved lines lines */
173 static const char *move_forward2 (const char *c
, int lines
)
179 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
; 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
; p
--){
195 if (*p
== CHAR_NODE_END
)
197 /* We reached the beginning of the node */
198 /* Skip the node headers */
199 while (*p
!= ']') p
++;
200 return currentpoint
= p
+ 2; /* Skip the newline following the start of the node */
202 if (*(p
- 1) == '\n')
205 return currentpoint
= p
;
207 return currentpoint
= c
;
210 static void move_forward (int i
)
214 currentpoint
= move_forward2 (currentpoint
, i
);
217 static void move_backward (int i
)
219 currentpoint
= move_backward2 (currentpoint
, ++i
);
222 static void move_to_top (void)
224 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
226 while (*currentpoint
!= ']')
228 currentpoint
= currentpoint
+ 2; /* Skip the newline following the start of the node */
229 selected_item
= NULL
;
232 static void move_to_bottom (void)
234 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
237 move_backward (help_lines
- 1);
240 static const char *help_follow_link (const char *start
, const char *selected_item
)
242 char link_name
[MAXLINKNAME
];
249 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
251 if (*p
== CHAR_LINK_POINTER
){
253 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
254 link_name
[i
++] = *++p
;
255 link_name
[i
-1] = ']';
257 p
= search_string (data
, link_name
);
259 p
+= 1; /* Skip the newline following the start of the node */
264 /* Create a replacement page with the error message */
265 return _(" Help file format error\n");
268 static const char *select_next_link (const char *current_link
)
275 p
= search_string_node (current_link
, STRING_LINK_END
);
278 p
= search_string_node (p
, STRING_LINK_START
);
284 static const char *select_prev_link (const char *current_link
)
289 return search_char_node (current_link
- 1, CHAR_LINK_START
, -1);
292 static void start_link_area (int x
, int y
, const char *link_name
)
296 if (inside_link_area
)
297 message (D_NORMAL
, _("Warning"), _(" Internal bug: Double start of link area "));
299 /* Allocate memory for a new link area */
300 new = g_new (Link_Area
, 1);
301 new->next
= link_area
;
304 /* Save the beginning coordinates of the link area */
308 /* Save the name of the destination anchor */
309 link_area
->link_name
= link_name
;
311 inside_link_area
= 1;
314 static void end_link_area (int x
, int y
)
316 if (inside_link_area
){
317 /* Save the end coordinates of the link area */
321 inside_link_area
= 0;
325 static void clear_link_areas (void)
331 link_area
= current
-> next
;
334 inside_link_area
= 0;
337 static void help_show (Dlg_head
*h
, const char *paint_start
)
342 int acs
; /* Flag: Alternate character set active? */
344 int active_col
, active_line
;/* Active link position */
346 attrset (HELP_NORMAL_COLOR
);
349 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
352 if (selected_item
< paint_start
)
353 selected_item
= NULL
;
355 for (p
= paint_start
; *p
&& *p
!= CHAR_NODE_END
&& line
< help_lines
; p
++) {
356 c
= (unsigned char)*p
;
358 case CHAR_LINK_START
:
359 if (selected_item
== NULL
)
361 if (p
== selected_item
){
362 attrset (HELP_SLINK_COLOR
);
364 /* Store the coordinates of the link */
365 active_col
= col
+ 2;
366 active_line
= line
+ 2;
369 attrset (HELP_LINK_COLOR
);
370 start_link_area (col
, line
, p
);
372 case CHAR_LINK_POINTER
:
374 end_link_area (col
- 1, line
);
378 attrset (HELP_NORMAL_COLOR
);
387 dlg_move (h
, line
+2, col
+2);
389 col
+= strlen (VERSION
);
392 attrset (HELP_BOLD_COLOR
);
394 case CHAR_FONT_ITALIC
:
395 attrset (HELP_ITALIC_COLOR
);
397 case CHAR_FONT_NORMAL
:
398 attrset (HELP_NORMAL_COLOR
);
405 col
= (col
/8 + 1) * 8;
410 if (col
> HELP_WINDOW_WIDTH
-1)
413 dlg_move (h
, line
+2, col
+2);
415 if (c
== ' ' || c
== '.')
421 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
430 end_of_node
= line
< help_lines
;
431 attrset (HELP_NORMAL_COLOR
);
432 if (selected_item
>= last_shown
){
433 if (link_area
!= NULL
){
434 selected_item
= link_area
->link_name
;
438 selected_item
= NULL
;
440 } while (repeat_paint
);
442 /* Position the cursor over a nice link */
444 dlg_move (h
, active_line
, active_col
);
448 help_event (Gpm_Event
*event
, void *vp
)
451 Link_Area
*current_area
;
453 if (! (event
->type
& GPM_UP
))
456 /* The event is relative to the dialog window, adjust it: */
460 if (event
->buttons
& GPM_B_RIGHT
){
461 currentpoint
= history
[history_ptr
].page
;
462 selected_item
= history
[history_ptr
].link
;
465 history_ptr
= HISTORY_SIZE
-1;
467 help_callback (w
->parent
, DLG_DRAW
, 0);
471 /* Test whether the mouse click is inside one of the link areas */
472 current_area
= link_area
;
475 /* Test one line link area */
476 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
477 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
479 /* Test two line link area */
480 if (current_area
->y1
+ 1 == current_area
->y2
){
482 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
484 /* The second line */
485 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
488 /* Mouse will not work with link areas of more than two lines */
490 current_area
= current_area
-> next
;
493 /* Test whether a link area was found */
495 /* The click was inside a link area -> follow the link */
496 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
497 history
[history_ptr
].page
= currentpoint
;
498 history
[history_ptr
].link
= current_area
->link_name
;
499 currentpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
500 selected_item
= NULL
;
503 move_backward (help_lines
- 1);
504 else if (event
->y
>= help_lines
)
505 move_forward (help_lines
- 1);
506 else if (event
->y
< help_lines
/2)
512 /* Show the new node */
513 help_callback (w
->parent
, DLG_DRAW
, 0);
520 help_help_cmd (void *vp
)
525 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
526 history
[history_ptr
].page
= currentpoint
;
527 history
[history_ptr
].link
= selected_item
;
529 p
= search_string(data
, "[How to use help]");
533 currentpoint
= p
+ 1; /* Skip the newline following the start of the node */
534 selected_item
= NULL
;
535 help_callback (h
, DLG_DRAW
, 0);
539 help_index_cmd (void *vp
)
542 const char *new_item
;
544 if (!(new_item
= search_string (data
, "[Contents]"))) {
545 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
550 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
551 history
[history_ptr
].page
= currentpoint
;
552 history
[history_ptr
].link
= selected_item
;
554 currentpoint
= new_item
+ 1; /* Skip the newline following the start of the node */
555 selected_item
= NULL
;
556 help_callback (h
, DLG_DRAW
, 0);
559 static void help_quit_cmd (void *vp
)
561 dlg_stop ((Dlg_head
*) vp
);
564 static void prev_node_cmd (void *vp
)
567 currentpoint
= history
[history_ptr
].page
;
568 selected_item
= history
[history_ptr
].link
;
571 history_ptr
= HISTORY_SIZE
-1;
573 help_callback (h
, DLG_DRAW
, 0);
577 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
580 return default_proc (msg
, parm
);
584 mousedispatch_new (int y
, int x
, int yl
, int xl
)
586 Widget
*w
= g_new (Widget
, 1);
588 init_widget (w
, y
, x
, yl
, xl
, md_callback
, help_event
);
593 static void help_cmk_move_backward(void *vp
, int lines
) {
595 move_backward(lines
);
597 static void help_cmk_move_forward(void *vp
, int lines
) {
601 static void help_cmk_moveto_top(void *vp
, int lines
) {
606 static void help_cmk_moveto_bottom(void *vp
, int lines
) {
613 help_handle_key (struct Dlg_head
*h
, int c
)
615 const char *new_item
;
617 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
618 check_movement_keys (c
, help_lines
, NULL
,
619 help_cmk_move_backward
,
620 help_cmk_move_forward
,
622 help_cmk_moveto_bottom
)) {
634 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
635 /* Is there any reason why the right key would take us
636 * backward if there are no links selected?, I agree
637 * with Torben than doing nothing in this case is better
639 /* If there are no links, go backward in history */
642 history_ptr
= HISTORY_SIZE
-1;
644 currentpoint
= history
[history_ptr
].page
;
645 selected_item
= history
[history_ptr
].link
;
648 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
649 history
[history_ptr
].page
= currentpoint
;
650 history
[history_ptr
].link
= selected_item
;
651 currentpoint
= help_follow_link (currentpoint
, selected_item
);
653 selected_item
= NULL
;
658 new_item
= select_next_link (selected_item
);
660 selected_item
= new_item
;
661 if (selected_item
>= last_shown
){
665 selected_item
= NULL
;
667 } else if (c
== KEY_DOWN
)
670 selected_item
= NULL
;
675 /* select previous link */
676 new_item
= select_prev_link (selected_item
);
677 selected_item
= new_item
;
678 if (selected_item
== NULL
|| selected_item
< currentpoint
) {
682 if (link_area
!= NULL
)
683 selected_item
= link_area
->link_name
;
685 selected_item
= NULL
;
692 new_item
= currentpoint
;
693 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
695 if (*++new_item
== '['){
696 while (*++new_item
) {
697 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
698 currentpoint
= new_item
;
699 selected_item
= NULL
;
708 new_item
= currentpoint
;
709 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
712 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
714 while (*new_item
!= ']')
716 currentpoint
= new_item
+ 2;
717 selected_item
= NULL
;
730 return MSG_NOT_HANDLED
;
733 help_callback (h
, DLG_DRAW
, 0);
738 help_callback (struct Dlg_head
*h
, dlg_msg_t msg
, int parm
)
742 common_dialog_repaint (h
);
743 help_show (h
, currentpoint
);
747 return help_handle_key (h
, parm
);
750 return default_dlg_callback (h
, msg
, parm
);
755 interactive_display_finish (void)
762 interactive_display (const char *filename
, const char *node
)
764 WButtonBar
*help_bar
;
766 char *hlpfile
= NULL
;
769 data
= load_file (filename
);
771 data
= load_mc_home_file ("mc.hlp", &hlpfile
);
774 message (D_ERROR
, MSG_ERROR
, _(" Cannot open file %s \n %s "), filename
? filename
: hlpfile
,
775 unix_error_string (errno
));
787 if (!(main_node
= search_string (data
, node
))) {
788 message (D_ERROR
, MSG_ERROR
, _(" Cannot find node %s in help file "),
791 /* Fallback to [main], return if it also cannot be found */
792 main_node
= search_string (data
, "[main]");
794 interactive_display_finish ();
799 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
802 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
803 dialog_colors
, help_callback
, "[Help]", _("Help"),
804 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
806 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
807 currentpoint
= main_node
+ 1; /* Skip the newline following the start of the node */
809 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
811 history
[history_ptr
].page
= currentpoint
;
812 history
[history_ptr
].link
= selected_item
;
815 help_bar
= buttonbar_new (1);
816 ((Widget
*) help_bar
)->y
-= whelp
->y
;
817 ((Widget
*) help_bar
)->x
-= whelp
->x
;
819 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
821 add_widget (whelp
, md
);
822 add_widget (whelp
, help_bar
);
824 buttonbar_set_label_data (whelp
, 1, _("Help"), help_help_cmd
, whelp
);
825 buttonbar_set_label_data (whelp
, 2, _("Index"), help_index_cmd
, whelp
);
826 buttonbar_set_label_data (whelp
, 3, _("Prev"), prev_node_cmd
, whelp
);
827 buttonbar_clear_label (whelp
, 4);
828 buttonbar_clear_label (whelp
, 5);
829 buttonbar_clear_label (whelp
, 6);
830 buttonbar_clear_label (whelp
, 7);
831 buttonbar_clear_label (whelp
, 8);
832 buttonbar_clear_label (whelp
, 9);
833 buttonbar_set_label_data (whelp
, 10, _("Quit"), help_quit_cmd
, whelp
);
836 interactive_display_finish ();