*** empty log message ***
[midnight-commander.git] / src / help.c
blob0736f7e801198be6572f6980c82e7402973e02ac
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 */
56 #define MAXLINKNAME 80
57 #define HISTORY_SIZE 20
58 #define HELP_WINDOW_WIDTH 62
60 /* "$Id$" */
62 static char *data; /* Pointer to the loaded data file */
63 static int help_lines = 18; /* Lines in help viewer */
64 static int history_ptr; /* For the history queue */
65 static char *main_node; /* The main node */
66 static char *last_shown = 0; /* Last byte shown in a screen */
67 static int end_of_node = 0; /* Flag: the last character of the node shown? */
68 static char *currentpoint, *startpoint;
69 static char *selected_item;
71 /* The widget variables */
72 static Dlg_head *whelp;
74 static struct {
75 char *page; /* Pointer to the selected page */
76 char *link; /* Pointer to the selected link */
77 } history [HISTORY_SIZE];
79 /* Link areas for the mouse */
80 typedef struct Link_Area {
81 int x1, y1, x2, y2;
82 char *link_name;
83 struct Link_Area *next;
84 } Link_Area;
86 static Link_Area *link_area = NULL;
87 static int inside_link_area = 0;
89 static int help_callback (struct Dlg_head *h, int id, int msg);
91 #ifdef HAS_ACS_AS_PCCHARS
92 static const struct {
93 int acscode;
94 int pccode;
95 } acs2pc_table [] = {
96 { 'q', 0xC4 },
97 { 'x', 0xB3 },
98 { 'l', 0xDA },
99 { 'k', 0xBF },
100 { 'm', 0xC0 },
101 { 'j', 0xD9 },
102 { 'a', 0xB0 },
103 { 'u', 0xB4 },
104 { 't', 0xC3 },
105 { 'w', 0xC2 },
106 { 'v', 0xC1 },
107 { 'n', 0xC5 },
108 { 0, 0 } };
110 static int acs2pc (int acscode)
112 int i;
114 for (i = 0; acs2pc_table[i].acscode != 0; i++)
115 if (acscode == acs2pc_table[i].acscode) {
116 return acs2pc_table[i].pccode;
118 return 0;
120 #endif
122 /* returns the position where text was found in the start buffer */
123 /* or 0 if not found */
124 static char *
125 search_string (char *start, char *text)
127 char *d = text;
128 char *e = start;
130 /* fmt sometimes replaces a space with a newline in the help file */
131 /* Replace the newlines in the link name with spaces to correct the situation */
132 while (*d){
133 if (*d == '\n')
134 *d = ' ';
135 d++;
137 /* Do search */
138 for (d = text; *e; e++){
139 if (*d == *e)
140 d++;
141 else
142 d = text;
143 if (!*d)
144 return e+1;
146 return 0;
149 /* Searches text in the buffer pointed by start. Search ends */
150 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
151 static char *search_string_node (char *start, char *text)
153 char *d = text;
154 char *e = start;
156 if (!start)
157 return 0;
159 for (; *e && *e != CHAR_NODE_END; e++){
160 if (*d == *e)
161 d++;
162 else
163 d = text;
164 if (!*d)
165 return e+1;
167 return 0;
170 /* Searches the_char in the buffer pointer by start and searches */
171 /* it can search forward (direction = 1) or backward (direction = -1) */
172 static char *search_char_node (char *start, char the_char, int direction)
174 char *e;
176 e = start;
178 for (; *e && (*e != CHAR_NODE_END); e += direction){
179 if (*e == the_char)
180 return e;
182 return 0;
185 /* Returns the new current pointer when moved lines lines */
186 static char *move_forward2 (char *c, int lines)
188 char *p;
189 int line;
191 currentpoint = c;
192 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
193 if (line == lines)
194 return currentpoint = p;
195 if (*p == '\n')
196 line++;
198 return currentpoint = c;
201 static char *move_backward2 (char *c, int lines)
203 char *p;
204 int line;
206 currentpoint = c;
207 for (line = 0, p = currentpoint; *p && p >= data; p--){
208 if (*p == CHAR_NODE_END)
210 /* We reached the beginning of the node */
211 /* Skip the node headers */
212 while (*p != ']') p++;
213 return currentpoint = p + 2;
215 if (*(p - 1) == '\n')
216 line++;
217 if (line == lines)
218 return currentpoint = p;
220 return currentpoint = c;
223 static void move_forward (int i)
225 if (end_of_node)
226 return;
227 currentpoint = move_forward2 (currentpoint, i);
230 static void move_backward (int i)
232 currentpoint = move_backward2 (currentpoint, ++i);
235 static void move_to_top (int dummy)
237 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
238 currentpoint--;
239 while (*currentpoint != ']')
240 currentpoint++;
241 currentpoint = currentpoint + 1;
242 selected_item = NULL;
245 static void move_to_bottom (int dummy)
247 while (*currentpoint && *currentpoint != CHAR_NODE_END)
248 currentpoint++;
249 currentpoint--;
250 move_backward (help_lines - 1);
253 static char *help_follow_link (char *start, char *selected_item)
255 char link_name [MAXLINKNAME];
256 char *p;
257 int i = 0;
259 if (!selected_item)
260 return start;
262 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
264 if (*p == CHAR_LINK_POINTER){
265 link_name [0] = '[';
266 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
267 link_name [i++] = *++p;
268 link_name [i-1] = ']';
269 link_name [i] = 0;
270 p = search_string (data, link_name);
271 if (p)
272 return p;
274 return _(" Help file format error\n\x4"); /* */
277 static char *select_next_link (char *start, char *current_link)
279 char *p;
281 if (!current_link)
282 return 0;
284 p = search_string_node (current_link, STRING_LINK_END);
285 if (!p)
286 return NULL;
287 p = search_string_node (p, STRING_LINK_START);
288 if (!p)
289 return NULL;
290 return p - 1;
293 static char *select_prev_link (char *start, char *current_link)
295 char *p;
297 if (!current_link)
298 return 0;
300 p = current_link - 1;
301 if (p <= start)
302 return 0;
304 p = search_char_node (p, CHAR_LINK_START, -1);
305 return p;
308 static void start_link_area (int x, int y, char *link_name)
310 Link_Area *new;
312 if (inside_link_area)
313 message (0, _(" Warning "), _(" Internal bug: Double start of link area "));
315 /* Allocate memory for a new link area */
316 new = g_new (Link_Area, 1);
317 new->next = link_area;
318 link_area = new;
320 /* Save the beginning coordinates of the link area */
321 link_area->x1 = x;
322 link_area->y1 = y;
324 /* Save the name of the destination anchor */
325 link_area->link_name = link_name;
327 inside_link_area = 1;
330 static void end_link_area (int x, int y)
332 if (inside_link_area){
333 /* Save the end coordinates of the link area */
334 link_area->x2 = x;
335 link_area->y2 = y;
337 inside_link_area = 0;
341 static void clear_link_areas (void)
343 Link_Area *current;
345 while (link_area){
346 current = link_area;
347 link_area = current -> next;
348 g_free (current);
350 inside_link_area = 0;
353 static void show (Dlg_head *h, char *paint_start)
355 char *p;
356 int col, line, c;
357 int painting = 1;
358 int acs; /* Flag: Alternate character set active? */
359 int repeat_paint;
360 int active_col, active_line;/* Active link position */
362 do {
364 line = col = acs = active_col = active_line = repeat_paint = 0;
366 clear_link_areas ();
367 if (selected_item < paint_start)
368 selected_item = NULL;
370 for (p = paint_start; *p != CHAR_NODE_END && line < help_lines; p++){
371 c = (unsigned char)*p;
372 switch (c){
373 case CHAR_LINK_START:
374 if (selected_item == NULL)
375 selected_item = p;
376 if (p == selected_item){
377 attrset (HELP_SLINK_COLOR);
379 /* Store the coordinates of the link */
380 active_col = col + 2;
381 active_line = line + 2;
383 else
384 attrset (HELP_LINK_COLOR);
385 start_link_area (col, line, p);
386 break;
387 case CHAR_LINK_POINTER:
388 painting = 0;
389 end_link_area (col - 1, line);
390 break;
391 case CHAR_LINK_END:
392 painting = 1;
393 attrset (HELP_NORMAL_COLOR);
394 break;
395 case CHAR_ALTERNATE:
396 acs = 1;
397 break;
398 case CHAR_NORMAL:
399 acs = 0;
400 break;
401 case CHAR_VERSION:
402 dlg_move (h, line+2, col+2);
403 addstr (VERSION);
404 col += strlen (VERSION);
405 break;
406 case CHAR_BOLD_ON:
407 attrset (HELP_BOLD_COLOR);
408 break;
409 case CHAR_ITALIC_ON:
410 attrset (HELP_ITALIC_COLOR);
411 break;
412 case CHAR_BOLD_OFF:
413 attrset (HELP_NORMAL_COLOR);
414 break;
415 case '\n':
416 line++;
417 col = 0;
418 break;
419 case '\t':
420 col = (col/8 + 1) * 8;
421 break;
422 case CHAR_MCLOGO:
423 case CHAR_TEXTONLY_START:
424 case CHAR_TEXTONLY_END:
425 break;
426 case CHAR_XONLY_START:
427 while (*p && *p != CHAR_NODE_END && *p != CHAR_XONLY_END)
428 p++;
429 if (*p == CHAR_NODE_END || !*p)
430 p--;
431 break;
432 default:
433 if (!painting)
434 continue;
435 if (col > HELP_WINDOW_WIDTH-1)
436 continue;
438 dlg_move (h, line+2, col+2);
439 if (acs){
440 if (c == ' ' || c == '.')
441 addch (c);
442 else
443 #ifndef OS2_NT
444 #ifndef HAVE_SLANG
445 addch (acs_map [c]);
446 #else
447 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
448 #endif
449 #else
450 addch (acs2pc (c));
451 #endif /* OS2_NT */
452 } else
453 addch (c);
454 col++;
455 break;
458 last_shown = p;
459 end_of_node = line < help_lines;
460 attrset (HELP_NORMAL_COLOR);
461 if (selected_item >= last_shown){
462 if (link_area != NULL){
463 selected_item = link_area->link_name;
464 repeat_paint = 1;
466 else
467 selected_item = NULL;
469 } while (repeat_paint);
471 /* Position the cursor over a nice link */
472 if (active_col)
473 dlg_move (h, active_line, active_col);
476 static int help_event (Gpm_Event *event, Widget *w)
478 Link_Area *current_area;
480 if (! (event->type & GPM_UP))
481 return 0;
483 /* The event is relative to the dialog window, adjust it: */
484 event->y -= 2;
486 if (event->buttons & GPM_B_RIGHT){
487 currentpoint = startpoint = history [history_ptr].page;
488 selected_item = history [history_ptr].link;
489 history_ptr--;
490 if (history_ptr < 0)
491 history_ptr = HISTORY_SIZE-1;
493 help_callback (w->parent, 0, DLG_DRAW);
494 return 0;
497 /* Test whether the mouse click is inside one of the link areas */
498 current_area = link_area;
499 while (current_area)
501 /* Test one line link area */
502 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
503 event->y == current_area->y2 && event->x <= current_area->x2)
504 break;
505 /* Test two line link area */
506 if (current_area->y1 + 1 == current_area->y2){
507 /* The first line */
508 if (event->y == current_area->y1 && event->x >= current_area->x1)
509 break;
510 /* The second line */
511 if (event->y == current_area->y2 && event->x <= current_area->x2)
512 break;
514 /* Mouse will not work with link areas of more than two lines */
516 current_area = current_area -> next;
519 /* Test whether a link area was found */
520 if (current_area){
521 /* The click was inside a link area -> follow the link */
522 history_ptr = (history_ptr+1) % HISTORY_SIZE;
523 history [history_ptr].page = currentpoint;
524 history [history_ptr].link = current_area->link_name;
525 currentpoint = startpoint = help_follow_link (currentpoint, current_area->link_name);
526 selected_item = NULL;
527 } else{
528 if (event->y < 0)
529 move_backward (help_lines - 1);
530 else if (event->y >= help_lines)
531 move_forward (help_lines - 1);
532 else if (event->y < help_lines/2)
533 move_backward (1);
534 else
535 move_forward (1);
538 /* Show the new node */
539 help_callback (w->parent, 0, DLG_DRAW);
541 return 0;
544 /* show help */
545 static void
546 help_help_cmd (Dlg_head *h)
548 history_ptr = (history_ptr+1) % HISTORY_SIZE;
549 history [history_ptr].page = currentpoint;
550 history [history_ptr].link = selected_item;
551 currentpoint = startpoint = search_string (data, "[How to use help]") + 1;
552 selected_item = NULL;
553 help_callback (h, 0, DLG_DRAW);
556 static void
557 help_index_cmd (Dlg_head *h)
559 char *new_item;
561 history_ptr = (history_ptr+1) % HISTORY_SIZE;
562 history [history_ptr].page = currentpoint;
563 history [history_ptr].link = selected_item;
564 currentpoint = startpoint = search_string (data, "[Help]") + 1;
566 if (!(new_item = search_string (data, "[Contents]")))
567 message (1, MSG_ERROR, _(" Cannot find node [Contents] in help file "));
568 else
569 currentpoint = startpoint = new_item + 1;
570 selected_item = NULL;
571 help_callback (h, 0, DLG_DRAW);
574 static void quit_cmd (void *x)
576 dlg_stop ((Dlg_head *)x);
579 static void prev_node_cmd (Dlg_head *h)
581 currentpoint = startpoint = history [history_ptr].page;
582 selected_item = history [history_ptr].link;
583 history_ptr--;
584 if (history_ptr < 0)
585 history_ptr = HISTORY_SIZE-1;
587 help_callback (h, 0, DLG_DRAW);
590 static int md_callback (Dlg_head *h, Widget *w, int msg, int par)
592 return default_proc (h, msg, par);
595 static Widget *mousedispatch_new (int y, int x, int yl, int xl)
597 Widget *w = g_new (Widget, 1);
599 init_widget (w, y, x, yl, xl,
600 (callback_fn) md_callback, 0, (mouse_h) help_event, NULL);
602 return w;
605 static int help_handle_key (struct Dlg_head *h, int c)
607 char *new_item;
609 if (c != KEY_UP && c != KEY_DOWN &&
610 check_movement_keys (c, 1, help_lines, currentpoint,
611 (movefn) move_backward2,
612 (movefn) move_forward2,
613 (movefn) move_to_top,
614 (movefn) move_to_bottom))
615 /* Nothing */;
616 else switch (c){
617 case 'l':
618 case KEY_LEFT:
619 prev_node_cmd (h);
620 break;
622 case '\n':
623 case KEY_RIGHT:
624 /* follow link */
625 if (!selected_item){
626 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
627 /* Is there any reason why the right key would take us
628 * backward if there are no links selected?, I agree
629 * with Torben than doing nothing in this case is better
631 /* If there are no links, go backward in history */
632 history_ptr--;
633 if (history_ptr < 0)
634 history_ptr = HISTORY_SIZE-1;
636 currentpoint = startpoint = history [history_ptr].page;
637 selected_item = history [history_ptr].link;
638 #endif
639 } else {
640 history_ptr = (history_ptr+1) % HISTORY_SIZE;
641 history [history_ptr].page = currentpoint;
642 history [history_ptr].link = selected_item;
643 currentpoint = startpoint = help_follow_link (currentpoint, selected_item) + 1;
645 selected_item = NULL;
646 break;
648 case KEY_DOWN:
649 case '\t':
650 /* select next link */
651 new_item = select_next_link (startpoint, selected_item);
652 if (new_item){
653 selected_item = new_item;
654 if (selected_item >= last_shown){
655 if (c == KEY_DOWN)
656 move_forward (1);
657 else
658 selected_item = NULL;
660 } else if (c == KEY_DOWN)
661 move_forward (1);
662 else
663 selected_item = NULL;
664 break;
666 case KEY_UP:
667 case ALT ('\t'):
668 /* select previous link */
669 new_item = select_prev_link (startpoint, selected_item);
670 selected_item = new_item;
671 if (selected_item < currentpoint || selected_item >= last_shown){
672 if (c == KEY_UP)
673 move_backward (1);
674 else{
675 if (link_area != NULL)
676 selected_item = link_area->link_name;
677 else
678 selected_item = NULL;
681 break;
683 case 'n':
684 /* Next node */
685 new_item = currentpoint;
686 while (*new_item && *new_item != CHAR_NODE_END)
687 new_item++;
688 if (*++new_item == '['){
689 while (*new_item != ']')
690 new_item++;
691 currentpoint = new_item + 2;
692 selected_item = NULL;
694 break;
696 case 'p':
697 /* Previous node */
698 new_item = currentpoint;
699 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
700 new_item--;
701 new_item--;
702 while (new_item > data && *new_item != CHAR_NODE_END)
703 new_item--;
704 while (*new_item != ']')
705 new_item++;
706 currentpoint = new_item + 2;
707 selected_item = NULL;
708 break;
710 case 'c':
711 help_index_cmd (h);
712 break;
714 case ESC_CHAR:
715 case XCTRL('g'):
716 dlg_stop (h);
717 break;
719 default:
720 return 0;
723 help_callback (h, 0, DLG_DRAW);
724 return 1;
727 static int help_callback (struct Dlg_head *h, int id, int msg)
729 switch (msg){
730 case DLG_DRAW:
731 attrset (HELP_NORMAL_COLOR);
732 dlg_erase (h);
733 draw_box (h, 1, 1, help_lines+2, HELP_WINDOW_WIDTH+2);
734 attrset (COLOR_HOT_NORMAL);
735 dlg_move (h, 1, (HELP_WINDOW_WIDTH - 1) / 2);
736 addstr (_(" Help "));
737 attrset (HELP_NORMAL_COLOR);
738 show (h, currentpoint);
739 break;
741 case DLG_KEY:
742 return help_handle_key (h, id);
744 return 0;
747 static void
748 interactive_display_finish (void)
750 clear_link_areas ();
751 g_free (data);
754 void
755 interactive_display (char *filename, char *node)
757 WButtonBar *help_bar;
758 Widget *md;
759 char *hlpfile = filename;
761 if (filename)
762 data = load_file (filename);
763 else
764 data = load_mc_home_file ("mc.hlp", &hlpfile);
766 if (data == NULL){
767 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "),
768 hlpfile, unix_error_string (errno));
771 if (!filename)
772 g_free (hlpfile);
774 if (!data)
775 return;
777 if (!(main_node = search_string (data, node))){
778 message (1, MSG_ERROR, _(" Cannot find node %s in help file "), node);
779 interactive_display_finish ();
780 return;
783 if (help_lines > LINES - 4)
784 help_lines = LINES - 4;
786 whelp = create_dlg (0, 0, help_lines+4, HELP_WINDOW_WIDTH+4, dialog_colors,
787 help_callback, "[Help]", "help",
788 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
790 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
791 currentpoint = startpoint = main_node + 1;
793 for (history_ptr = HISTORY_SIZE; history_ptr;){
794 history_ptr--;
795 history [history_ptr].page = currentpoint;
796 history [history_ptr].link = selected_item;
799 help_bar = buttonbar_new (1);
800 help_bar->widget.y -= whelp->y;
801 help_bar->widget.x -= whelp->x;
803 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH-2);
805 add_widget (whelp, help_bar);
806 add_widget (whelp, md);
808 define_label_data (whelp, (Widget *)NULL, 1, _("Help"),
809 (buttonbarfn) help_help_cmd, whelp);
810 define_label_data (whelp, (Widget *)NULL, 2, _("Index"),
811 (buttonbarfn) help_index_cmd,whelp);
812 define_label_data (whelp, (Widget *)NULL, 3, _("Prev"),
813 (buttonbarfn) prev_node_cmd, whelp);
814 define_label (whelp, (Widget *) NULL, 4, "", 0);
815 define_label (whelp, (Widget *) NULL, 5, "", 0);
816 define_label (whelp, (Widget *) NULL, 6, "", 0);
817 define_label (whelp, (Widget *) NULL, 7, "", 0);
818 define_label (whelp, (Widget *) NULL, 8, "", 0);
819 define_label (whelp, (Widget *) NULL, 9, "", 0);
820 define_label_data (whelp, (Widget *) NULL, 10, _("Quit"), quit_cmd, whelp);
822 run_dlg (whelp);
823 interactive_display_finish ();
824 destroy_dlg (whelp);