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>
51 #include "key.h" /* For mi_getch() */
53 #include "dlg.h" /* For Dlg_head */
54 #include "widget.h" /* For Widget */
55 #include "wtools.h" /* For common_dialog_repaint() */
57 #define MAXLINKNAME 80
58 #define HISTORY_SIZE 20
59 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
61 #define STRING_LINK_START "\01"
62 #define STRING_LINK_POINTER "\02"
63 #define STRING_LINK_END "\03"
64 #define STRING_NODE_END "\04"
66 static char *data
; /* Pointer to the loaded data file */
67 static int help_lines
; /* Lines in help viewer */
68 static int history_ptr
; /* For the history queue */
69 static char *main_node
; /* The main node */
70 static char *last_shown
= 0; /* Last byte shown in a screen */
71 static int end_of_node
= 0; /* Flag: the last character of the node shown? */
72 static char *currentpoint
, *startpoint
;
73 static char *selected_item
;
75 /* The widget variables */
76 static Dlg_head
*whelp
;
79 char *page
; /* Pointer to the selected page */
80 char *link
; /* Pointer to the selected link */
81 } history
[HISTORY_SIZE
];
83 /* Link areas for the mouse */
84 typedef struct Link_Area
{
87 struct Link_Area
*next
;
90 static Link_Area
*link_area
= NULL
;
91 static int inside_link_area
= 0;
93 static int help_callback (struct Dlg_head
*h
, int id
, int msg
);
95 #ifdef HAS_ACS_AS_PCCHARS
114 static int acs2pc (int acscode
)
118 for (i
= 0; acs2pc_table
[i
].acscode
!= 0; i
++)
119 if (acscode
== acs2pc_table
[i
].acscode
) {
120 return acs2pc_table
[i
].pccode
;
126 /* returns the position where text was found in the start buffer */
127 /* or 0 if not found */
129 search_string (char *start
, char *text
)
134 /* fmt sometimes replaces a space with a newline in the help file */
135 /* Replace the newlines in the link name with spaces to correct the situation */
142 for (d
= text
; *e
; e
++){
153 /* Searches text in the buffer pointed by start. Search ends */
154 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
155 static char *search_string_node (char *start
, char *text
)
163 for (; *e
&& *e
!= CHAR_NODE_END
; e
++){
174 /* Searches the_char in the buffer pointer by start and searches */
175 /* it can search forward (direction = 1) or backward (direction = -1) */
176 static char *search_char_node (char *start
, char the_char
, int direction
)
182 for (; *e
&& (*e
!= CHAR_NODE_END
); e
+= direction
){
189 /* Returns the new current pointer when moved lines lines */
190 static char *move_forward2 (char *c
, int lines
)
196 for (line
= 0, p
= currentpoint
; *p
&& *p
!= CHAR_NODE_END
; p
++){
198 return currentpoint
= p
;
202 return currentpoint
= c
;
205 static char *move_backward2 (char *c
, int lines
)
211 for (line
= 0, p
= currentpoint
; *p
&& p
>= data
; p
--){
212 if (*p
== CHAR_NODE_END
)
214 /* We reached the beginning of the node */
215 /* Skip the node headers */
216 while (*p
!= ']') p
++;
217 return currentpoint
= p
+ 2;
219 if (*(p
- 1) == '\n')
222 return currentpoint
= p
;
224 return currentpoint
= c
;
227 static void move_forward (int i
)
231 currentpoint
= move_forward2 (currentpoint
, i
);
234 static void move_backward (int i
)
236 currentpoint
= move_backward2 (currentpoint
, ++i
);
239 static void move_to_top (int dummy
)
241 while (currentpoint
> data
&& *currentpoint
!= CHAR_NODE_END
)
243 while (*currentpoint
!= ']')
245 currentpoint
= currentpoint
+ 1;
246 selected_item
= NULL
;
249 static void move_to_bottom (int dummy
)
251 while (*currentpoint
&& *currentpoint
!= CHAR_NODE_END
)
254 move_backward (help_lines
- 1);
257 static char *help_follow_link (char *start
, char *selected_item
)
259 char link_name
[MAXLINKNAME
];
266 for (p
= selected_item
; *p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_LINK_POINTER
; p
++)
268 if (*p
== CHAR_LINK_POINTER
){
270 for (i
= 1; *p
!= CHAR_LINK_END
&& *p
&& *p
!= CHAR_NODE_END
&& i
< MAXLINKNAME
-3; )
271 link_name
[i
++] = *++p
;
272 link_name
[i
-1] = ']';
274 p
= search_string (data
, link_name
);
278 return _(" Help file format error\n\x4"); /* */
281 static char *select_next_link (char *start
, char *current_link
)
288 p
= search_string_node (current_link
, STRING_LINK_END
);
291 p
= search_string_node (p
, STRING_LINK_START
);
297 static char *select_prev_link (char *start
, char *current_link
)
304 p
= current_link
- 1;
308 p
= search_char_node (p
, CHAR_LINK_START
, -1);
312 static void start_link_area (int x
, int y
, char *link_name
)
316 if (inside_link_area
)
317 message (0, _(" Warning "), _(" Internal bug: Double start of link area "));
319 /* Allocate memory for a new link area */
320 new = g_new (Link_Area
, 1);
321 new->next
= link_area
;
324 /* Save the beginning coordinates of the link area */
328 /* Save the name of the destination anchor */
329 link_area
->link_name
= link_name
;
331 inside_link_area
= 1;
334 static void end_link_area (int x
, int y
)
336 if (inside_link_area
){
337 /* Save the end coordinates of the link area */
341 inside_link_area
= 0;
345 static void clear_link_areas (void)
351 link_area
= current
-> next
;
354 inside_link_area
= 0;
357 static void show (Dlg_head
*h
, char *paint_start
)
362 int acs
; /* Flag: Alternate character set active? */
364 int active_col
, active_line
;/* Active link position */
366 attrset (HELP_NORMAL_COLOR
);
369 line
= col
= acs
= active_col
= active_line
= repeat_paint
= 0;
372 if (selected_item
< paint_start
)
373 selected_item
= NULL
;
375 for (p
= paint_start
; *p
&& *p
!= CHAR_NODE_END
&& line
< help_lines
; p
++) {
376 c
= (unsigned char)*p
;
378 case CHAR_LINK_START
:
379 if (selected_item
== NULL
)
381 if (p
== selected_item
){
382 attrset (HELP_SLINK_COLOR
);
384 /* Store the coordinates of the link */
385 active_col
= col
+ 2;
386 active_line
= line
+ 2;
389 attrset (HELP_LINK_COLOR
);
390 start_link_area (col
, line
, p
);
392 case CHAR_LINK_POINTER
:
394 end_link_area (col
- 1, line
);
398 attrset (HELP_NORMAL_COLOR
);
407 dlg_move (h
, line
+2, col
+2);
409 col
+= strlen (VERSION
);
412 attrset (HELP_BOLD_COLOR
);
414 case CHAR_FONT_ITALIC
:
415 attrset (HELP_ITALIC_COLOR
);
417 case CHAR_FONT_NORMAL
:
418 attrset (HELP_NORMAL_COLOR
);
425 col
= (col
/8 + 1) * 8;
428 case CHAR_TEXTONLY_START
:
429 case CHAR_TEXTONLY_END
:
431 case CHAR_XONLY_START
:
432 while (*p
&& *p
!= CHAR_NODE_END
&& *p
!= CHAR_XONLY_END
)
434 if (*p
== CHAR_NODE_END
|| !*p
)
440 if (col
> HELP_WINDOW_WIDTH
-1)
443 dlg_move (h
, line
+2, col
+2);
445 if (c
== ' ' || c
== '.')
452 SLsmg_draw_object (h
->y
+ line
+ 2, h
->x
+ col
+ 2, c
);
456 #endif /* NATIVE_WIN32 */
464 end_of_node
= line
< help_lines
;
465 attrset (HELP_NORMAL_COLOR
);
466 if (selected_item
>= last_shown
){
467 if (link_area
!= NULL
){
468 selected_item
= link_area
->link_name
;
472 selected_item
= NULL
;
474 } while (repeat_paint
);
476 /* Position the cursor over a nice link */
478 dlg_move (h
, active_line
, active_col
);
481 static int help_event (Gpm_Event
*event
, Widget
*w
)
483 Link_Area
*current_area
;
485 if (! (event
->type
& GPM_UP
))
488 /* The event is relative to the dialog window, adjust it: */
491 if (event
->buttons
& GPM_B_RIGHT
){
492 currentpoint
= startpoint
= history
[history_ptr
].page
;
493 selected_item
= history
[history_ptr
].link
;
496 history_ptr
= HISTORY_SIZE
-1;
498 help_callback (w
->parent
, 0, DLG_DRAW
);
502 /* Test whether the mouse click is inside one of the link areas */
503 current_area
= link_area
;
506 /* Test one line link area */
507 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
&&
508 event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
510 /* Test two line link area */
511 if (current_area
->y1
+ 1 == current_area
->y2
){
513 if (event
->y
== current_area
->y1
&& event
->x
>= current_area
->x1
)
515 /* The second line */
516 if (event
->y
== current_area
->y2
&& event
->x
<= current_area
->x2
)
519 /* Mouse will not work with link areas of more than two lines */
521 current_area
= current_area
-> next
;
524 /* Test whether a link area was found */
526 /* The click was inside a link area -> follow the link */
527 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
528 history
[history_ptr
].page
= currentpoint
;
529 history
[history_ptr
].link
= current_area
->link_name
;
530 currentpoint
= startpoint
= help_follow_link (currentpoint
, current_area
->link_name
);
531 selected_item
= NULL
;
534 move_backward (help_lines
- 1);
535 else if (event
->y
>= help_lines
)
536 move_forward (help_lines
- 1);
537 else if (event
->y
< help_lines
/2)
543 /* Show the new node */
544 help_callback (w
->parent
, 0, DLG_DRAW
);
551 help_help_cmd (Dlg_head
*h
)
553 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
554 history
[history_ptr
].page
= currentpoint
;
555 history
[history_ptr
].link
= selected_item
;
556 currentpoint
= startpoint
= search_string (data
, "[How to use help]") + 1;
557 selected_item
= NULL
;
558 help_callback (h
, 0, DLG_DRAW
);
562 help_index_cmd (Dlg_head
* h
)
566 if (!(new_item
= search_string (data
, "[Contents]"))) {
567 message (1, MSG_ERROR
, _(" Cannot find node %s in help file "),
572 history_ptr
= (history_ptr
+ 1) % HISTORY_SIZE
;
573 history
[history_ptr
].page
= currentpoint
;
574 history
[history_ptr
].link
= selected_item
;
576 currentpoint
= startpoint
= new_item
+ 1;
577 selected_item
= NULL
;
578 help_callback (h
, 0, DLG_DRAW
);
581 static void quit_cmd (void *x
)
583 dlg_stop ((Dlg_head
*)x
);
586 static void prev_node_cmd (Dlg_head
*h
)
588 currentpoint
= startpoint
= history
[history_ptr
].page
;
589 selected_item
= history
[history_ptr
].link
;
592 history_ptr
= HISTORY_SIZE
-1;
594 help_callback (h
, 0, DLG_DRAW
);
597 static int md_callback (Dlg_head
*h
, Widget
*w
, int msg
, int par
)
599 return default_proc (h
, msg
, par
);
602 static Widget
*mousedispatch_new (int y
, int x
, int yl
, int xl
)
604 Widget
*w
= g_new (Widget
, 1);
606 init_widget (w
, y
, x
, yl
, xl
,
607 (callback_fn
) md_callback
, 0, (mouse_h
) help_event
, NULL
);
612 static int help_handle_key (struct Dlg_head
*h
, int c
)
616 if (c
!= KEY_UP
&& c
!= KEY_DOWN
&&
617 check_movement_keys (c
, 1, help_lines
, currentpoint
,
618 (movefn
) move_backward2
,
619 (movefn
) move_forward2
,
620 (movefn
) move_to_top
,
621 (movefn
) move_to_bottom
))
633 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
634 /* Is there any reason why the right key would take us
635 * backward if there are no links selected?, I agree
636 * with Torben than doing nothing in this case is better
638 /* If there are no links, go backward in history */
641 history_ptr
= HISTORY_SIZE
-1;
643 currentpoint
= startpoint
= history
[history_ptr
].page
;
644 selected_item
= history
[history_ptr
].link
;
647 history_ptr
= (history_ptr
+1) % HISTORY_SIZE
;
648 history
[history_ptr
].page
= currentpoint
;
649 history
[history_ptr
].link
= selected_item
;
650 currentpoint
= startpoint
= help_follow_link (currentpoint
, selected_item
) + 1;
652 selected_item
= NULL
;
657 /* select next link */
658 new_item
= select_next_link (startpoint
, 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 (startpoint
, selected_item
);
677 selected_item
= new_item
;
678 if (selected_item
< currentpoint
|| selected_item
>= last_shown
){
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
;
733 help_callback (h
, 0, DLG_DRAW
);
737 static int help_callback (struct Dlg_head
*h
, int id
, int msg
)
741 common_dialog_repaint (h
);
742 show (h
, currentpoint
);
746 return help_handle_key (h
, id
);
752 interactive_display_finish (void)
759 interactive_display (char *filename
, char *node
)
761 WButtonBar
*help_bar
;
763 char *hlpfile
= filename
;
766 data
= load_file (filename
);
768 data
= load_mc_home_file ("mc.hlp", &hlpfile
);
771 message (1, MSG_ERROR
, _(" Cannot open file %s \n %s "), hlpfile
,
772 unix_error_string (errno
));
784 if (!(main_node
= search_string (data
, node
))) {
785 message (1, 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
= startpoint
= main_node
+ 1;
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 help_bar
->widget
.y
-= whelp
->y
;
814 help_bar
->widget
.x
-= whelp
->x
;
816 md
= mousedispatch_new (1, 1, help_lines
, HELP_WINDOW_WIDTH
- 2);
818 add_widget (whelp
, help_bar
);
819 add_widget (whelp
, md
);
821 define_label_data (whelp
, (Widget
*) NULL
, 1, _("Help"),
822 (buttonbarfn
) help_help_cmd
, whelp
);
823 define_label_data (whelp
, (Widget
*) NULL
, 2, _("Index"),
824 (buttonbarfn
) help_index_cmd
, whelp
);
825 define_label_data (whelp
, (Widget
*) NULL
, 3, _("Prev"),
826 (buttonbarfn
) prev_node_cmd
, whelp
);
827 define_label (whelp
, (Widget
*) NULL
, 4, "", 0);
828 define_label (whelp
, (Widget
*) NULL
, 5, "", 0);
829 define_label (whelp
, (Widget
*) NULL
, 6, "", 0);
830 define_label (whelp
, (Widget
*) NULL
, 7, "", 0);
831 define_label (whelp
, (Widget
*) NULL
, 8, "", 0);
832 define_label (whelp
, (Widget
*) NULL
, 9, "", 0);
833 define_label_data (whelp
, (Widget
*) NULL
, 10, _("Quit"), quit_cmd
,
837 interactive_display_finish ();