1 /* Hypertext file browser.
2 Copyright (C) 1994, 1995 Miguel de Icaza.
3 Copyright (C) 1994, 1995 Janne Kukonlehto
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 Implements the hypertext file viewer.
20 The hypertext file is a file that may have one or more nodes. Each
21 node ends with a ^D character and starts with a bracket, then the
22 name of the node and then a closing bracket.
24 Links in the hypertext file are specified like this: the text that
25 will be highlighted should have a leading ^A, then it comes the
26 text, then a ^B indicating that highlighting is done, then the name
27 of the node you want to link to and then a ^C.
29 The file must contain a ^D at the beginning and at the end of the
30 file or the program will not be able to detect the end of file.
32 Lazyness/widgeting attack: This file does use the dialog manager
33 and uses mainly the dialog to achieve the help work. there is only
34 one specialized widget and it's only used to forward the mouse messages
35 to the appropiate routine.
41 #include <sys/types.h>
50 #include "key.h" /* For mi_getch() */
52 #include "dialog.h" /* For Dlg_head */
53 #include "widget.h" /* For Widget */
54 #include "wtools.h" /* For common_dialog_repaint() */
56 #define MAXLINKNAME 80
57 #define HISTORY_SIZE 20
58 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
60 #define STRING_LINK_START "\01"
61 #define STRING_LINK_POINTER "\02"
62 #define STRING_LINK_END "\03"
63 #define STRING_NODE_END "\04"
65 static char *data
; /* Pointer to the loaded data file */
66 static int help_lines
; /* Lines in help viewer */
67 static int history_ptr
; /* For the history queue */
68 static char *main_node
; /* The main node */
69 static char *last_shown
= 0; /* Last byte shown in a screen */
70 static int end_of_node
= 0; /* Flag: the last character of the node shown? */
71 static char *currentpoint
, *startpoint
;
72 static char *selected_item
;
74 /* The widget variables */
75 static Dlg_head
*whelp
;
78 char *page
; /* Pointer to the selected page */
79 char *link
; /* Pointer to the selected link */
80 } history
[HISTORY_SIZE
];
82 /* Link areas for the mouse */
83 typedef struct Link_Area
{
86 struct Link_Area
*next
;
89 static Link_Area
*link_area
= NULL
;
90 static int inside_link_area
= 0;
92 static cb_ret_t
help_callback (struct Dlg_head
*h
, dlg_msg_t
, int parm
);
94 #ifdef HAS_ACS_AS_PCCHARS
113 static int acs2pc (int acscode
)
117 for (i
= 0; acs2pc_table
[i
].acscode
!= 0; i
++)
118 if (acscode
== acs2pc_table
[i
].acscode
) {
119 return acs2pc_table
[i
].pccode
;
125 /* returns the position where text was found in the start buffer */
126 /* or 0 if not found */
128 search_string (char *start
, char *text
)
133 /* fmt sometimes replaces a space with a newline in the help file */
134 /* Replace the newlines in the link name with spaces to correct the situation */
141 for (d
= text
; *e
; e
++){
152 /* Searches text in the buffer pointed by start. Search ends */
153 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
154 static char *search_string_node (char *start
, char *text
)
162 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
173 /* Searches the_char in the buffer pointer by start and searches */
174 /* it can search forward (direction = 1) or backward (direction = -1) */
175 static char *search_char_node (char *start
, char the_char
, int direction
)
181 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
188 /* Returns the new current pointer when moved lines lines */
189 static char *move_forward2 (char *c
, int lines
)
195 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
; p
++){
197 return currentpoint
= p
;
201 return currentpoint
= c
;
204 static char *move_backward2 (char *c
, int lines
)
210 for (line
= 0, p
= currentpoint
; *p
&& p
>= data
; p
--){
211 if (*p
== CHAR_NODE_END
)
213 /* We reached the beginning of the node */
214 /* Skip the node headers */
215 while (*p
!= ']') p
++;
216 return currentpoint
= p
+ 2;
218 if (*(p
- 1) == '\n')
221 return currentpoint
= p
;
223 return currentpoint
= c
;
226 static void move_forward (int i
)
230 currentpoint
= move_forward2 (currentpoint
, i
);
233 static void move_backward (int i
)
235 currentpoint
= move_backward2 (currentpoint
, ++i
);
238 static void move_to_top (int dummy
)
240 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
242 while (*currentpoint
!= ']')
244 currentpoint
= currentpoint
+ 1;
245 selected_item
= NULL
;
248 static void move_to_bottom (int dummy
)
250 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
253 move_backward (help_lines
- 1);
256 static char *help_follow_link (char *start
, char *selected_item
)
258 char link_name
[MAXLINKNAME
];
265 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
267 if (*p
== CHAR_LINK_POINTER
){
269 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
270 link_name
[i
++] = *++p
;
271 link_name
[i
-1] = ']';
273 p
= search_string (data
, link_name
);
278 /* Create a replacement page with the error message */
279 return _(" Help file format error\n");
282 static char *select_next_link (char *start
, char *current_link
)
289 p
= search_string_node (current_link
, STRING_LINK_END
);
292 p
= search_string_node (p
, STRING_LINK_START
);
298 static char *select_prev_link (char *start
, char *current_link
)
305 p
= current_link
- 1;
309 p
= search_char_node (p
, CHAR_LINK_START
, -1);
313 static void start_link_area (int x
, int y
, char *link_name
)
317 if (inside_link_area
)
318 message (0, _("Warning"), _(" Internal bug: Double start of link area "));
320 /* Allocate memory for a new link area */
321 new = g_new (Link_Area
, 1);
322 new->next
= link_area
;
325 /* Save the beginning coordinates of the link area */
329 /* Save the name of the destination anchor */
330 link_area
->link_name
= link_name
;
332 inside_link_area
= 1;
335 static void end_link_area (int x
, int y
)
337 if (inside_link_area
){
338 /* Save the end coordinates of the link area */
342 inside_link_area
= 0;
346 static void clear_link_areas (void)
352 link_area
= current
-> next
;
355 inside_link_area
= 0;
358 static void help_show (Dlg_head
*h
, char *paint_start
)
363 int acs
; /* Flag: Alternate character set active? */
365 int active_col
, active_line
;/* Active link position */
367 attrset (HELP_NORMAL_COLOR
);
370 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
373 if (selected_item
< paint_start
)
374 selected_item
= NULL
;
376 for (p
= paint_start
; *p
&& *p
!= CHAR_NODE_END
&& line
< help_lines
; p
++) {
377 c
= (unsigned char)*p
;
379 case CHAR_LINK_START
:
380 if (selected_item
== NULL
)
382 if (p
== selected_item
){
383 attrset (HELP_SLINK_COLOR
);
385 /* Store the coordinates of the link */
386 active_col
= col
+ 2;
387 active_line
= line
+ 2;
390 attrset (HELP_LINK_COLOR
);
391 start_link_area (col
, line
, p
);
393 case CHAR_LINK_POINTER
:
395 end_link_area (col
- 1, line
);
399 attrset (HELP_NORMAL_COLOR
);
408 dlg_move (h
, line
+2, col
+2);
410 col
+= strlen (VERSION
);
413 attrset (HELP_BOLD_COLOR
);
415 case CHAR_FONT_ITALIC
:
416 attrset (HELP_ITALIC_COLOR
);
418 case CHAR_FONT_NORMAL
:
419 attrset (HELP_NORMAL_COLOR
);
426 col
= (col
/8 + 1) * 8;
431 if (col
> HELP_WINDOW_WIDTH
-1)
434 dlg_move (h
, line
+2, col
+2);
436 if (c
== ' ' || c
== '.')
442 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
451 end_of_node
= line
< help_lines
;
452 attrset (HELP_NORMAL_COLOR
);
453 if (selected_item
>= last_shown
){
454 if (link_area
!= NULL
){
455 selected_item
= link_area
->link_name
;
459 selected_item
= NULL
;
461 } while (repeat_paint
);
463 /* Position the cursor over a nice link */
465 dlg_move (h
, active_line
, active_col
);
468 static int help_event (Gpm_Event
*event
, Widget
*w
)
470 Link_Area
*current_area
;
472 if (! (event
->type
& GPM_UP
))
475 /* The event is relative to the dialog window, adjust it: */
478 if (event
->buttons
& GPM_B_RIGHT
){
479 currentpoint
= startpoint
= history
[history_ptr
].page
;
480 selected_item
= history
[history_ptr
].link
;
483 history_ptr
= HISTORY_SIZE
-1;
485 help_callback (w
->parent
, DLG_DRAW
, 0);
489 /* Test whether the mouse click is inside one of the link areas */
490 current_area
= link_area
;
493 /* Test one line link area */
494 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
495 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
497 /* Test two line link area */
498 if (current_area
->y1
+ 1 == current_area
->y2
){
500 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
502 /* The second line */
503 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
506 /* Mouse will not work with link areas of more than two lines */
508 current_area
= current_area
-> next
;
511 /* Test whether a link area was found */
513 /* The click was inside a link area -> follow the link */
514 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
515 history
[history_ptr
].page
= currentpoint
;
516 history
[history_ptr
].link
= current_area
->link_name
;
517 currentpoint
= startpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
518 selected_item
= NULL
;
521 move_backward (help_lines
- 1);
522 else if (event
->y
>= help_lines
)
523 move_forward (help_lines
- 1);
524 else if (event
->y
< help_lines
/2)
530 /* Show the new node */
531 help_callback (w
->parent
, DLG_DRAW
, 0);
538 help_help_cmd (Dlg_head
*h
)
540 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
541 history
[history_ptr
].page
= currentpoint
;
542 history
[history_ptr
].link
= selected_item
;
543 currentpoint
= startpoint
= search_string (data
, "[How to use help]") + 1;
544 selected_item
= NULL
;
545 help_callback (h
, DLG_DRAW
, 0);
549 help_index_cmd (Dlg_head
* h
)
553 if (!(new_item
= search_string (data
, "[Contents]"))) {
554 message (1, MSG_ERROR
, _(" Cannot find node %s in help file "),
559 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
560 history
[history_ptr
].page
= currentpoint
;
561 history
[history_ptr
].link
= selected_item
;
563 currentpoint
= startpoint
= new_item
+ 1;
564 selected_item
= NULL
;
565 help_callback (h
, DLG_DRAW
, 0);
568 static void help_quit_cmd (void *x
)
570 dlg_stop ((Dlg_head
*)x
);
573 static void prev_node_cmd (Dlg_head
*h
)
575 currentpoint
= startpoint
= history
[history_ptr
].page
;
576 selected_item
= history
[history_ptr
].link
;
579 history_ptr
= HISTORY_SIZE
-1;
581 help_callback (h
, DLG_DRAW
, 0);
585 md_callback (Widget
*w
, widget_msg_t msg
, int parm
)
587 return default_proc (msg
, parm
);
591 mousedispatch_new (int y
, int x
, int yl
, int xl
)
593 Widget
*w
= g_new (Widget
, 1);
595 init_widget (w
, y
, x
, yl
, xl
, (callback_fn
) md_callback
,
596 (mouse_h
) help_event
);
602 help_handle_key (struct Dlg_head
*h
, int c
)
606 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
607 check_movement_keys (c
, help_lines
, currentpoint
,
608 (movefn
) move_backward2
,
609 (movefn
) move_forward2
,
610 (movefn
) move_to_top
,
611 (movefn
) move_to_bottom
))
623 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
624 /* Is there any reason why the right key would take us
625 * backward if there are no links selected?, I agree
626 * with Torben than doing nothing in this case is better
628 /* If there are no links, go backward in history */
631 history_ptr
= HISTORY_SIZE
-1;
633 currentpoint
= startpoint
= history
[history_ptr
].page
;
634 selected_item
= history
[history_ptr
].link
;
637 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
638 history
[history_ptr
].page
= currentpoint
;
639 history
[history_ptr
].link
= selected_item
;
640 currentpoint
= startpoint
= help_follow_link (currentpoint
, selected_item
) + 1;
642 selected_item
= NULL
;
647 /* select next link */
648 new_item
= select_next_link (startpoint
, selected_item
);
650 selected_item
= new_item
;
651 if (selected_item
>= last_shown
){
655 selected_item
= NULL
;
657 } else if (c
== KEY_DOWN
)
660 selected_item
= NULL
;
665 /* select previous link */
666 new_item
= select_prev_link (startpoint
, selected_item
);
667 selected_item
= new_item
;
668 if (selected_item
< currentpoint
|| selected_item
>= last_shown
){
672 if (link_area
!= NULL
)
673 selected_item
= link_area
->link_name
;
675 selected_item
= NULL
;
682 new_item
= currentpoint
;
683 while (*new_item
&& *new_item
!= CHAR_NODE_END
)
685 if (*++new_item
== '['){
686 while (*++new_item
) {
687 if (*new_item
== ']' && *++new_item
&& *++new_item
) {
688 currentpoint
= new_item
;
689 selected_item
= NULL
;
698 new_item
= currentpoint
;
699 while (new_item
> data
+ 1 && *new_item
!= CHAR_NODE_END
)
702 while (new_item
> data
&& *new_item
!= CHAR_NODE_END
)
704 while (*new_item
!= ']')
706 currentpoint
= new_item
+ 2;
707 selected_item
= NULL
;
720 return MSG_NOT_HANDLED
;
723 help_callback (h
, DLG_DRAW
, 0);
728 help_callback (struct Dlg_head
*h
, dlg_msg_t msg
, int parm
)
732 common_dialog_repaint (h
);
733 help_show (h
, currentpoint
);
737 return help_handle_key (h
, parm
);
740 return default_dlg_callback (h
, msg
, parm
);
745 interactive_display_finish (void)
752 interactive_display (char *filename
, char *node
)
754 WButtonBar
*help_bar
;
756 char *hlpfile
= filename
;
759 data
= load_file (filename
);
761 data
= load_mc_home_file ("mc.hlp", &hlpfile
);
764 message (1, MSG_ERROR
, _(" Cannot open file %s \n %s "), hlpfile
,
765 unix_error_string (errno
));
777 if (!(main_node
= search_string (data
, node
))) {
778 message (1, MSG_ERROR
, _(" Cannot find node %s in help file "),
781 /* Fallback to [main], return if it also cannot be found */
782 main_node
= search_string (data
, "[main]");
784 interactive_display_finish ();
789 help_lines
= min (LINES
- 4, max (2 * LINES
/ 3, 18));
792 create_dlg (0, 0, help_lines
+ 4, HELP_WINDOW_WIDTH
+ 4,
793 dialog_colors
, help_callback
, "[Help]", _("Help"),
794 DLG_TRYUP
| DLG_CENTER
| DLG_WANT_TAB
);
796 selected_item
= search_string_node (main_node
, STRING_LINK_START
) - 1;
797 currentpoint
= startpoint
= main_node
+ 1;
799 for (history_ptr
= HISTORY_SIZE
; history_ptr
;) {
801 history
[history_ptr
].page
= currentpoint
;
802 history
[history_ptr
].link
= selected_item
;
805 help_bar
= buttonbar_new (1);
806 help_bar
->widget
.y
-= whelp
->y
;
807 help_bar
->widget
.x
-= whelp
->x
;
809 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
811 add_widget (whelp
, md
);
812 add_widget (whelp
, help_bar
);
814 define_label_data (whelp
, 1, _("Help"), (buttonbarfn
) help_help_cmd
,
816 define_label_data (whelp
, 2, _("Index"), (buttonbarfn
) help_index_cmd
,
818 define_label_data (whelp
, 3, _("Prev"), (buttonbarfn
) prev_node_cmd
,
820 define_label (whelp
, 4, "", 0);
821 define_label (whelp
, 5, "", 0);
822 define_label (whelp
, 6, "", 0);
823 define_label (whelp
, 7, "", 0);
824 define_label (whelp
, 8, "", 0);
825 define_label (whelp
, 9, "", 0);
826 define_label_data (whelp
, 10, _("Quit"), help_quit_cmd
, whelp
);
829 interactive_display_finish ();