- Use the included S-Lang again, since we include a better version now.
[midnight-commander.git] / src / help.c
blob4c5b7a2ebdf07dac8b09c2cc100984db0ab26d25
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.
39 #include <config.h>
40 #include <stdio.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <errno.h>
45 #include "global.h"
46 #include "tty.h"
47 #include "color.h"
48 #include "dialog.h"
49 #include "win.h"
50 #include "mouse.h"
51 #include "key.h" /* For mi_getch() */
52 #include "help.h"
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;
78 static struct {
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 {
85 int x1, y1, x2, y2;
86 char *link_name;
87 struct Link_Area *next;
88 } Link_Area;
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
96 static const struct {
97 int acscode;
98 int pccode;
99 } acs2pc_table [] = {
100 { 'q', 0xC4 },
101 { 'x', 0xB3 },
102 { 'l', 0xDA },
103 { 'k', 0xBF },
104 { 'm', 0xC0 },
105 { 'j', 0xD9 },
106 { 'a', 0xB0 },
107 { 'u', 0xB4 },
108 { 't', 0xC3 },
109 { 'w', 0xC2 },
110 { 'v', 0xC1 },
111 { 'n', 0xC5 },
112 { 0, 0 } };
114 static int acs2pc (int acscode)
116 int i;
118 for (i = 0; acs2pc_table[i].acscode != 0; i++)
119 if (acscode == acs2pc_table[i].acscode) {
120 return acs2pc_table[i].pccode;
122 return 0;
124 #endif
126 /* returns the position where text was found in the start buffer */
127 /* or 0 if not found */
128 static char *
129 search_string (char *start, char *text)
131 char *d = text;
132 char *e = start;
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 */
136 while (*d){
137 if (*d == '\n')
138 *d = ' ';
139 d++;
141 /* Do search */
142 for (d = text; *e; e++){
143 if (*d == *e)
144 d++;
145 else
146 d = text;
147 if (!*d)
148 return e+1;
150 return 0;
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)
157 char *d = text;
158 char *e = start;
160 if (!start)
161 return 0;
163 for (; *e && *e != CHAR_NODE_END; e++){
164 if (*d == *e)
165 d++;
166 else
167 d = text;
168 if (!*d)
169 return e+1;
171 return 0;
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)
178 char *e;
180 e = start;
182 for (; *e && (*e != CHAR_NODE_END); e += direction){
183 if (*e == the_char)
184 return e;
186 return 0;
189 /* Returns the new current pointer when moved lines lines */
190 static char *move_forward2 (char *c, int lines)
192 char *p;
193 int line;
195 currentpoint = c;
196 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
197 if (line == lines)
198 return currentpoint = p;
199 if (*p == '\n')
200 line++;
202 return currentpoint = c;
205 static char *move_backward2 (char *c, int lines)
207 char *p;
208 int line;
210 currentpoint = c;
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')
220 line++;
221 if (line == lines)
222 return currentpoint = p;
224 return currentpoint = c;
227 static void move_forward (int i)
229 if (end_of_node)
230 return;
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)
242 currentpoint--;
243 while (*currentpoint != ']')
244 currentpoint++;
245 currentpoint = currentpoint + 1;
246 selected_item = NULL;
249 static void move_to_bottom (int dummy)
251 while (*currentpoint && *currentpoint != CHAR_NODE_END)
252 currentpoint++;
253 currentpoint--;
254 move_backward (help_lines - 1);
257 static char *help_follow_link (char *start, char *selected_item)
259 char link_name [MAXLINKNAME];
260 char *p;
261 int i = 0;
263 if (!selected_item)
264 return start;
266 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
268 if (*p == CHAR_LINK_POINTER){
269 link_name [0] = '[';
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] = ']';
273 link_name [i] = 0;
274 p = search_string (data, link_name);
275 if (p)
276 return p;
278 return _(" Help file format error\n\x4"); /* */
281 static char *select_next_link (char *start, char *current_link)
283 char *p;
285 if (!current_link)
286 return 0;
288 p = search_string_node (current_link, STRING_LINK_END);
289 if (!p)
290 return NULL;
291 p = search_string_node (p, STRING_LINK_START);
292 if (!p)
293 return NULL;
294 return p - 1;
297 static char *select_prev_link (char *start, char *current_link)
299 char *p;
301 if (!current_link)
302 return 0;
304 p = current_link - 1;
305 if (p <= start)
306 return 0;
308 p = search_char_node (p, CHAR_LINK_START, -1);
309 return p;
312 static void start_link_area (int x, int y, char *link_name)
314 Link_Area *new;
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;
322 link_area = new;
324 /* Save the beginning coordinates of the link area */
325 link_area->x1 = x;
326 link_area->y1 = y;
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 */
338 link_area->x2 = x;
339 link_area->y2 = y;
341 inside_link_area = 0;
345 static void clear_link_areas (void)
347 Link_Area *current;
349 while (link_area){
350 current = link_area;
351 link_area = current -> next;
352 g_free (current);
354 inside_link_area = 0;
357 static void show (Dlg_head *h, char *paint_start)
359 char *p;
360 int col, line, c;
361 int painting = 1;
362 int acs; /* Flag: Alternate character set active? */
363 int repeat_paint;
364 int active_col, active_line;/* Active link position */
366 attrset (HELP_NORMAL_COLOR);
367 do {
369 line = col = acs = active_col = active_line = repeat_paint = 0;
371 clear_link_areas ();
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;
377 switch (c){
378 case CHAR_LINK_START:
379 if (selected_item == NULL)
380 selected_item = p;
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;
388 else
389 attrset (HELP_LINK_COLOR);
390 start_link_area (col, line, p);
391 break;
392 case CHAR_LINK_POINTER:
393 painting = 0;
394 end_link_area (col - 1, line);
395 break;
396 case CHAR_LINK_END:
397 painting = 1;
398 attrset (HELP_NORMAL_COLOR);
399 break;
400 case CHAR_ALTERNATE:
401 acs = 1;
402 break;
403 case CHAR_NORMAL:
404 acs = 0;
405 break;
406 case CHAR_VERSION:
407 dlg_move (h, line+2, col+2);
408 addstr (VERSION);
409 col += strlen (VERSION);
410 break;
411 case CHAR_FONT_BOLD:
412 attrset (HELP_BOLD_COLOR);
413 break;
414 case CHAR_FONT_ITALIC:
415 attrset (HELP_ITALIC_COLOR);
416 break;
417 case CHAR_FONT_NORMAL:
418 attrset (HELP_NORMAL_COLOR);
419 break;
420 case '\n':
421 line++;
422 col = 0;
423 break;
424 case '\t':
425 col = (col/8 + 1) * 8;
426 break;
427 case CHAR_MCLOGO:
428 case CHAR_TEXTONLY_START:
429 case CHAR_TEXTONLY_END:
430 break;
431 case CHAR_XONLY_START:
432 while (*p && *p != CHAR_NODE_END && *p != CHAR_XONLY_END)
433 p++;
434 if (*p == CHAR_NODE_END || !*p)
435 p--;
436 break;
437 default:
438 if (!painting)
439 continue;
440 if (col > HELP_WINDOW_WIDTH-1)
441 continue;
443 dlg_move (h, line+2, col+2);
444 if (acs){
445 if (c == ' ' || c == '.')
446 addch (c);
447 else
448 #ifndef NATIVE_WIN32
449 #ifndef HAVE_SLANG
450 addch (acs_map [c]);
451 #else
452 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
453 #endif
454 #else
455 addch (acs2pc (c));
456 #endif /* NATIVE_WIN32 */
457 } else
458 addch (c);
459 col++;
460 break;
463 last_shown = p;
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;
469 repeat_paint = 1;
471 else
472 selected_item = NULL;
474 } while (repeat_paint);
476 /* Position the cursor over a nice link */
477 if (active_col)
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))
486 return 0;
488 /* The event is relative to the dialog window, adjust it: */
489 event->y -= 2;
491 if (event->buttons & GPM_B_RIGHT){
492 currentpoint = startpoint = history [history_ptr].page;
493 selected_item = history [history_ptr].link;
494 history_ptr--;
495 if (history_ptr < 0)
496 history_ptr = HISTORY_SIZE-1;
498 help_callback (w->parent, 0, DLG_DRAW);
499 return 0;
502 /* Test whether the mouse click is inside one of the link areas */
503 current_area = link_area;
504 while (current_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)
509 break;
510 /* Test two line link area */
511 if (current_area->y1 + 1 == current_area->y2){
512 /* The first line */
513 if (event->y == current_area->y1 && event->x >= current_area->x1)
514 break;
515 /* The second line */
516 if (event->y == current_area->y2 && event->x <= current_area->x2)
517 break;
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 */
525 if (current_area){
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;
532 } else{
533 if (event->y < 0)
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)
538 move_backward (1);
539 else
540 move_forward (1);
543 /* Show the new node */
544 help_callback (w->parent, 0, DLG_DRAW);
546 return 0;
549 /* show help */
550 static void
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);
561 static void
562 help_index_cmd (Dlg_head * h)
564 char *new_item;
566 if (!(new_item = search_string (data, "[Contents]"))) {
567 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
568 "[Contents]");
569 return;
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;
590 history_ptr--;
591 if (history_ptr < 0)
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);
609 return w;
612 static int help_handle_key (struct Dlg_head *h, int c)
614 char *new_item;
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))
622 /* Nothing */;
623 else switch (c){
624 case 'l':
625 case KEY_LEFT:
626 prev_node_cmd (h);
627 break;
629 case '\n':
630 case KEY_RIGHT:
631 /* follow link */
632 if (!selected_item){
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 */
639 history_ptr--;
640 if (history_ptr < 0)
641 history_ptr = HISTORY_SIZE-1;
643 currentpoint = startpoint = history [history_ptr].page;
644 selected_item = history [history_ptr].link;
645 #endif
646 } else {
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;
653 break;
655 case KEY_DOWN:
656 case '\t':
657 /* select next link */
658 new_item = select_next_link (startpoint, selected_item);
659 if (new_item){
660 selected_item = new_item;
661 if (selected_item >= last_shown){
662 if (c == KEY_DOWN)
663 move_forward (1);
664 else
665 selected_item = NULL;
667 } else if (c == KEY_DOWN)
668 move_forward (1);
669 else
670 selected_item = NULL;
671 break;
673 case KEY_UP:
674 case ALT ('\t'):
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){
679 if (c == KEY_UP)
680 move_backward (1);
681 else{
682 if (link_area != NULL)
683 selected_item = link_area->link_name;
684 else
685 selected_item = NULL;
688 break;
690 case 'n':
691 /* Next node */
692 new_item = currentpoint;
693 while (*new_item && *new_item != CHAR_NODE_END)
694 new_item++;
695 if (*++new_item == '['){
696 while (*++new_item) {
697 if (*new_item == ']' && *++new_item && *++new_item) {
698 currentpoint = new_item;
699 selected_item = NULL;
700 break;
704 break;
706 case 'p':
707 /* Previous node */
708 new_item = currentpoint;
709 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
710 new_item--;
711 new_item--;
712 while (new_item > data && *new_item != CHAR_NODE_END)
713 new_item--;
714 while (*new_item != ']')
715 new_item++;
716 currentpoint = new_item + 2;
717 selected_item = NULL;
718 break;
720 case 'c':
721 help_index_cmd (h);
722 break;
724 case ESC_CHAR:
725 case XCTRL('g'):
726 dlg_stop (h);
727 break;
729 default:
730 return 0;
733 help_callback (h, 0, DLG_DRAW);
734 return 1;
737 static int help_callback (struct Dlg_head *h, int id, int msg)
739 switch (msg){
740 case DLG_DRAW:
741 common_dialog_repaint (h);
742 show (h, currentpoint);
743 break;
745 case DLG_KEY:
746 return help_handle_key (h, id);
748 return 0;
751 static void
752 interactive_display_finish (void)
754 clear_link_areas ();
755 g_free (data);
758 void
759 interactive_display (char *filename, char *node)
761 WButtonBar *help_bar;
762 Widget *md;
763 char *hlpfile = filename;
765 if (filename)
766 data = load_file (filename);
767 else
768 data = load_mc_home_file ("mc.hlp", &hlpfile);
770 if (data == NULL) {
771 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "), hlpfile,
772 unix_error_string (errno));
775 if (!filename)
776 g_free (hlpfile);
778 if (!data)
779 return;
781 if (!node || !*node)
782 node = "[main]";
784 if (!(main_node = search_string (data, node))) {
785 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
786 node);
788 /* Fallback to [main], return if it also cannot be found */
789 main_node = search_string (data, "[main]");
790 if (!main_node) {
791 interactive_display_finish ();
792 return;
796 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
798 whelp =
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;) {
807 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,
834 whelp);
836 run_dlg (whelp);
837 interactive_display_finish ();
838 destroy_dlg (whelp);