*** empty log message ***
[midnight-commander.git] / src / help.c
blobe9ea80387ac3f1221154f5f0279cd0a608bc9568
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 62
61 /* "$Id$" */
63 static char *data; /* Pointer to the loaded data file */
64 static int help_lines; /* Lines in help viewer */
65 static int history_ptr; /* For the history queue */
66 static char *main_node; /* The main node */
67 static char *last_shown = 0; /* Last byte shown in a screen */
68 static int end_of_node = 0; /* Flag: the last character of the node shown? */
69 static char *currentpoint, *startpoint;
70 static char *selected_item;
72 /* The widget variables */
73 static Dlg_head *whelp;
75 static struct {
76 char *page; /* Pointer to the selected page */
77 char *link; /* Pointer to the selected link */
78 } history [HISTORY_SIZE];
80 /* Link areas for the mouse */
81 typedef struct Link_Area {
82 int x1, y1, x2, y2;
83 char *link_name;
84 struct Link_Area *next;
85 } Link_Area;
87 static Link_Area *link_area = NULL;
88 static int inside_link_area = 0;
90 static int help_callback (struct Dlg_head *h, int id, int msg);
92 #ifdef HAS_ACS_AS_PCCHARS
93 static const struct {
94 int acscode;
95 int pccode;
96 } acs2pc_table [] = {
97 { 'q', 0xC4 },
98 { 'x', 0xB3 },
99 { 'l', 0xDA },
100 { 'k', 0xBF },
101 { 'm', 0xC0 },
102 { 'j', 0xD9 },
103 { 'a', 0xB0 },
104 { 'u', 0xB4 },
105 { 't', 0xC3 },
106 { 'w', 0xC2 },
107 { 'v', 0xC1 },
108 { 'n', 0xC5 },
109 { 0, 0 } };
111 static int acs2pc (int acscode)
113 int i;
115 for (i = 0; acs2pc_table[i].acscode != 0; i++)
116 if (acscode == acs2pc_table[i].acscode) {
117 return acs2pc_table[i].pccode;
119 return 0;
121 #endif
123 /* returns the position where text was found in the start buffer */
124 /* or 0 if not found */
125 static char *
126 search_string (char *start, char *text)
128 char *d = text;
129 char *e = start;
131 /* fmt sometimes replaces a space with a newline in the help file */
132 /* Replace the newlines in the link name with spaces to correct the situation */
133 while (*d){
134 if (*d == '\n')
135 *d = ' ';
136 d++;
138 /* Do search */
139 for (d = text; *e; e++){
140 if (*d == *e)
141 d++;
142 else
143 d = text;
144 if (!*d)
145 return e+1;
147 return 0;
150 /* Searches text in the buffer pointed by start. Search ends */
151 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
152 static char *search_string_node (char *start, char *text)
154 char *d = text;
155 char *e = start;
157 if (!start)
158 return 0;
160 for (; *e && *e != CHAR_NODE_END; e++){
161 if (*d == *e)
162 d++;
163 else
164 d = text;
165 if (!*d)
166 return e+1;
168 return 0;
171 /* Searches the_char in the buffer pointer by start and searches */
172 /* it can search forward (direction = 1) or backward (direction = -1) */
173 static char *search_char_node (char *start, char the_char, int direction)
175 char *e;
177 e = start;
179 for (; *e && (*e != CHAR_NODE_END); e += direction){
180 if (*e == the_char)
181 return e;
183 return 0;
186 /* Returns the new current pointer when moved lines lines */
187 static char *move_forward2 (char *c, int lines)
189 char *p;
190 int line;
192 currentpoint = c;
193 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
194 if (line == lines)
195 return currentpoint = p;
196 if (*p == '\n')
197 line++;
199 return currentpoint = c;
202 static char *move_backward2 (char *c, int lines)
204 char *p;
205 int line;
207 currentpoint = c;
208 for (line = 0, p = currentpoint; *p && p >= data; p--){
209 if (*p == CHAR_NODE_END)
211 /* We reached the beginning of the node */
212 /* Skip the node headers */
213 while (*p != ']') p++;
214 return currentpoint = p + 2;
216 if (*(p - 1) == '\n')
217 line++;
218 if (line == lines)
219 return currentpoint = p;
221 return currentpoint = c;
224 static void move_forward (int i)
226 if (end_of_node)
227 return;
228 currentpoint = move_forward2 (currentpoint, i);
231 static void move_backward (int i)
233 currentpoint = move_backward2 (currentpoint, ++i);
236 static void move_to_top (int dummy)
238 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
239 currentpoint--;
240 while (*currentpoint != ']')
241 currentpoint++;
242 currentpoint = currentpoint + 1;
243 selected_item = NULL;
246 static void move_to_bottom (int dummy)
248 while (*currentpoint && *currentpoint != CHAR_NODE_END)
249 currentpoint++;
250 currentpoint--;
251 move_backward (help_lines - 1);
254 static char *help_follow_link (char *start, char *selected_item)
256 char link_name [MAXLINKNAME];
257 char *p;
258 int i = 0;
260 if (!selected_item)
261 return start;
263 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
265 if (*p == CHAR_LINK_POINTER){
266 link_name [0] = '[';
267 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
268 link_name [i++] = *++p;
269 link_name [i-1] = ']';
270 link_name [i] = 0;
271 p = search_string (data, link_name);
272 if (p)
273 return p;
275 return _(" Help file format error\n\x4"); /* */
278 static char *select_next_link (char *start, char *current_link)
280 char *p;
282 if (!current_link)
283 return 0;
285 p = search_string_node (current_link, STRING_LINK_END);
286 if (!p)
287 return NULL;
288 p = search_string_node (p, STRING_LINK_START);
289 if (!p)
290 return NULL;
291 return p - 1;
294 static char *select_prev_link (char *start, char *current_link)
296 char *p;
298 if (!current_link)
299 return 0;
301 p = current_link - 1;
302 if (p <= start)
303 return 0;
305 p = search_char_node (p, CHAR_LINK_START, -1);
306 return p;
309 static void start_link_area (int x, int y, char *link_name)
311 Link_Area *new;
313 if (inside_link_area)
314 message (0, _(" Warning "), _(" Internal bug: Double start of link area "));
316 /* Allocate memory for a new link area */
317 new = g_new (Link_Area, 1);
318 new->next = link_area;
319 link_area = new;
321 /* Save the beginning coordinates of the link area */
322 link_area->x1 = x;
323 link_area->y1 = y;
325 /* Save the name of the destination anchor */
326 link_area->link_name = link_name;
328 inside_link_area = 1;
331 static void end_link_area (int x, int y)
333 if (inside_link_area){
334 /* Save the end coordinates of the link area */
335 link_area->x2 = x;
336 link_area->y2 = y;
338 inside_link_area = 0;
342 static void clear_link_areas (void)
344 Link_Area *current;
346 while (link_area){
347 current = link_area;
348 link_area = current -> next;
349 g_free (current);
351 inside_link_area = 0;
354 static void show (Dlg_head *h, char *paint_start)
356 char *p;
357 int col, line, c;
358 int painting = 1;
359 int acs; /* Flag: Alternate character set active? */
360 int repeat_paint;
361 int active_col, active_line;/* Active link position */
363 attrset (HELP_NORMAL_COLOR);
364 do {
366 line = col = acs = active_col = active_line = repeat_paint = 0;
368 clear_link_areas ();
369 if (selected_item < paint_start)
370 selected_item = NULL;
372 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
373 c = (unsigned char)*p;
374 switch (c){
375 case CHAR_LINK_START:
376 if (selected_item == NULL)
377 selected_item = p;
378 if (p == selected_item){
379 attrset (HELP_SLINK_COLOR);
381 /* Store the coordinates of the link */
382 active_col = col + 2;
383 active_line = line + 2;
385 else
386 attrset (HELP_LINK_COLOR);
387 start_link_area (col, line, p);
388 break;
389 case CHAR_LINK_POINTER:
390 painting = 0;
391 end_link_area (col - 1, line);
392 break;
393 case CHAR_LINK_END:
394 painting = 1;
395 attrset (HELP_NORMAL_COLOR);
396 break;
397 case CHAR_ALTERNATE:
398 acs = 1;
399 break;
400 case CHAR_NORMAL:
401 acs = 0;
402 break;
403 case CHAR_VERSION:
404 dlg_move (h, line+2, col+2);
405 addstr (VERSION);
406 col += strlen (VERSION);
407 break;
408 case CHAR_BOLD_ON:
409 attrset (HELP_BOLD_COLOR);
410 break;
411 case CHAR_ITALIC_ON:
412 attrset (HELP_ITALIC_COLOR);
413 break;
414 case CHAR_BOLD_OFF:
415 attrset (HELP_NORMAL_COLOR);
416 break;
417 case '\n':
418 line++;
419 col = 0;
420 break;
421 case '\t':
422 col = (col/8 + 1) * 8;
423 break;
424 case CHAR_MCLOGO:
425 case CHAR_TEXTONLY_START:
426 case CHAR_TEXTONLY_END:
427 break;
428 case CHAR_XONLY_START:
429 while (*p && *p != CHAR_NODE_END && *p != CHAR_XONLY_END)
430 p++;
431 if (*p == CHAR_NODE_END || !*p)
432 p--;
433 break;
434 default:
435 if (!painting)
436 continue;
437 if (col > HELP_WINDOW_WIDTH-1)
438 continue;
440 dlg_move (h, line+2, col+2);
441 if (acs){
442 if (c == ' ' || c == '.')
443 addch (c);
444 else
445 #ifndef OS2_NT
446 #ifndef HAVE_SLANG
447 addch (acs_map [c]);
448 #else
449 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
450 #endif
451 #else
452 addch (acs2pc (c));
453 #endif /* OS2_NT */
454 } else
455 addch (c);
456 col++;
457 break;
460 last_shown = p;
461 end_of_node = line < help_lines;
462 attrset (HELP_NORMAL_COLOR);
463 if (selected_item >= last_shown){
464 if (link_area != NULL){
465 selected_item = link_area->link_name;
466 repeat_paint = 1;
468 else
469 selected_item = NULL;
471 } while (repeat_paint);
473 /* Position the cursor over a nice link */
474 if (active_col)
475 dlg_move (h, active_line, active_col);
478 static int help_event (Gpm_Event *event, Widget *w)
480 Link_Area *current_area;
482 if (! (event->type & GPM_UP))
483 return 0;
485 /* The event is relative to the dialog window, adjust it: */
486 event->y -= 2;
488 if (event->buttons & GPM_B_RIGHT){
489 currentpoint = startpoint = history [history_ptr].page;
490 selected_item = history [history_ptr].link;
491 history_ptr--;
492 if (history_ptr < 0)
493 history_ptr = HISTORY_SIZE-1;
495 help_callback (w->parent, 0, DLG_DRAW);
496 return 0;
499 /* Test whether the mouse click is inside one of the link areas */
500 current_area = link_area;
501 while (current_area)
503 /* Test one line link area */
504 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
505 event->y == current_area->y2 && event->x <= current_area->x2)
506 break;
507 /* Test two line link area */
508 if (current_area->y1 + 1 == current_area->y2){
509 /* The first line */
510 if (event->y == current_area->y1 && event->x >= current_area->x1)
511 break;
512 /* The second line */
513 if (event->y == current_area->y2 && event->x <= current_area->x2)
514 break;
516 /* Mouse will not work with link areas of more than two lines */
518 current_area = current_area -> next;
521 /* Test whether a link area was found */
522 if (current_area){
523 /* The click was inside a link area -> follow the link */
524 history_ptr = (history_ptr+1) % HISTORY_SIZE;
525 history [history_ptr].page = currentpoint;
526 history [history_ptr].link = current_area->link_name;
527 currentpoint = startpoint = help_follow_link (currentpoint, current_area->link_name);
528 selected_item = NULL;
529 } else{
530 if (event->y < 0)
531 move_backward (help_lines - 1);
532 else if (event->y >= help_lines)
533 move_forward (help_lines - 1);
534 else if (event->y < help_lines/2)
535 move_backward (1);
536 else
537 move_forward (1);
540 /* Show the new node */
541 help_callback (w->parent, 0, DLG_DRAW);
543 return 0;
546 /* show help */
547 static void
548 help_help_cmd (Dlg_head *h)
550 history_ptr = (history_ptr+1) % HISTORY_SIZE;
551 history [history_ptr].page = currentpoint;
552 history [history_ptr].link = selected_item;
553 currentpoint = startpoint = search_string (data, "[How to use help]") + 1;
554 selected_item = NULL;
555 help_callback (h, 0, DLG_DRAW);
558 static void
559 help_index_cmd (Dlg_head * h)
561 char *new_item;
563 if (!(new_item = search_string (data, "[Contents]"))) {
564 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
565 "[Contents]");
566 return;
569 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
570 history[history_ptr].page = currentpoint;
571 history[history_ptr].link = selected_item;
573 currentpoint = startpoint = new_item + 1;
574 selected_item = NULL;
575 help_callback (h, 0, DLG_DRAW);
578 static void quit_cmd (void *x)
580 dlg_stop ((Dlg_head *)x);
583 static void prev_node_cmd (Dlg_head *h)
585 currentpoint = startpoint = history [history_ptr].page;
586 selected_item = history [history_ptr].link;
587 history_ptr--;
588 if (history_ptr < 0)
589 history_ptr = HISTORY_SIZE-1;
591 help_callback (h, 0, DLG_DRAW);
594 static int md_callback (Dlg_head *h, Widget *w, int msg, int par)
596 return default_proc (h, msg, par);
599 static Widget *mousedispatch_new (int y, int x, int yl, int xl)
601 Widget *w = g_new (Widget, 1);
603 init_widget (w, y, x, yl, xl,
604 (callback_fn) md_callback, 0, (mouse_h) help_event, NULL);
606 return w;
609 static int help_handle_key (struct Dlg_head *h, int c)
611 char *new_item;
613 if (c != KEY_UP && c != KEY_DOWN &&
614 check_movement_keys (c, 1, help_lines, currentpoint,
615 (movefn) move_backward2,
616 (movefn) move_forward2,
617 (movefn) move_to_top,
618 (movefn) move_to_bottom))
619 /* Nothing */;
620 else switch (c){
621 case 'l':
622 case KEY_LEFT:
623 prev_node_cmd (h);
624 break;
626 case '\n':
627 case KEY_RIGHT:
628 /* follow link */
629 if (!selected_item){
630 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
631 /* Is there any reason why the right key would take us
632 * backward if there are no links selected?, I agree
633 * with Torben than doing nothing in this case is better
635 /* If there are no links, go backward in history */
636 history_ptr--;
637 if (history_ptr < 0)
638 history_ptr = HISTORY_SIZE-1;
640 currentpoint = startpoint = history [history_ptr].page;
641 selected_item = history [history_ptr].link;
642 #endif
643 } else {
644 history_ptr = (history_ptr+1) % HISTORY_SIZE;
645 history [history_ptr].page = currentpoint;
646 history [history_ptr].link = selected_item;
647 currentpoint = startpoint = help_follow_link (currentpoint, selected_item) + 1;
649 selected_item = NULL;
650 break;
652 case KEY_DOWN:
653 case '\t':
654 /* select next link */
655 new_item = select_next_link (startpoint, selected_item);
656 if (new_item){
657 selected_item = new_item;
658 if (selected_item >= last_shown){
659 if (c == KEY_DOWN)
660 move_forward (1);
661 else
662 selected_item = NULL;
664 } else if (c == KEY_DOWN)
665 move_forward (1);
666 else
667 selected_item = NULL;
668 break;
670 case KEY_UP:
671 case ALT ('\t'):
672 /* select previous link */
673 new_item = select_prev_link (startpoint, selected_item);
674 selected_item = new_item;
675 if (selected_item < currentpoint || selected_item >= last_shown){
676 if (c == KEY_UP)
677 move_backward (1);
678 else{
679 if (link_area != NULL)
680 selected_item = link_area->link_name;
681 else
682 selected_item = NULL;
685 break;
687 case 'n':
688 /* Next node */
689 new_item = currentpoint;
690 while (*new_item && *new_item != CHAR_NODE_END)
691 new_item++;
692 if (*++new_item == '['){
693 while (*++new_item) {
694 if (*new_item == ']' && *++new_item && *++new_item) {
695 currentpoint = new_item;
696 selected_item = NULL;
697 break;
701 break;
703 case 'p':
704 /* Previous node */
705 new_item = currentpoint;
706 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
707 new_item--;
708 new_item--;
709 while (new_item > data && *new_item != CHAR_NODE_END)
710 new_item--;
711 while (*new_item != ']')
712 new_item++;
713 currentpoint = new_item + 2;
714 selected_item = NULL;
715 break;
717 case 'c':
718 help_index_cmd (h);
719 break;
721 case ESC_CHAR:
722 case XCTRL('g'):
723 dlg_stop (h);
724 break;
726 default:
727 return 0;
730 help_callback (h, 0, DLG_DRAW);
731 return 1;
734 static int help_callback (struct Dlg_head *h, int id, int msg)
736 switch (msg){
737 case DLG_DRAW:
738 common_dialog_repaint (h);
739 show (h, currentpoint);
740 break;
742 case DLG_KEY:
743 return help_handle_key (h, id);
745 return 0;
748 static void
749 interactive_display_finish (void)
751 clear_link_areas ();
752 g_free (data);
755 void
756 interactive_display (char *filename, char *node)
758 WButtonBar *help_bar;
759 Widget *md;
760 char *hlpfile = filename;
762 if (filename)
763 data = load_file (filename);
764 else
765 data = load_mc_home_file ("mc.hlp", &hlpfile);
767 if (data == NULL) {
768 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "), hlpfile,
769 unix_error_string (errno));
772 if (!filename)
773 g_free (hlpfile);
775 if (!data)
776 return;
778 if (!node || !*node)
779 node = "[main]";
781 if (!(main_node = search_string (data, node))) {
782 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
783 node);
785 /* Fallback to [main], return if it also cannot be found */
786 main_node = search_string (data, "[main]");
787 if (!main_node) {
788 interactive_display_finish ();
789 return;
793 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
795 whelp =
796 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
797 dialog_colors, help_callback, "[Help]", _("Help"),
798 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
800 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
801 currentpoint = startpoint = main_node + 1;
803 for (history_ptr = HISTORY_SIZE; history_ptr;) {
804 history_ptr--;
805 history[history_ptr].page = currentpoint;
806 history[history_ptr].link = selected_item;
809 help_bar = buttonbar_new (1);
810 help_bar->widget.y -= whelp->y;
811 help_bar->widget.x -= whelp->x;
813 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
815 add_widget (whelp, help_bar);
816 add_widget (whelp, md);
818 define_label_data (whelp, (Widget *) NULL, 1, _("Help"),
819 (buttonbarfn) help_help_cmd, whelp);
820 define_label_data (whelp, (Widget *) NULL, 2, _("Index"),
821 (buttonbarfn) help_index_cmd, whelp);
822 define_label_data (whelp, (Widget *) NULL, 3, _("Prev"),
823 (buttonbarfn) prev_node_cmd, whelp);
824 define_label (whelp, (Widget *) NULL, 4, "", 0);
825 define_label (whelp, (Widget *) NULL, 5, "", 0);
826 define_label (whelp, (Widget *) NULL, 6, "", 0);
827 define_label (whelp, (Widget *) NULL, 7, "", 0);
828 define_label (whelp, (Widget *) NULL, 8, "", 0);
829 define_label (whelp, (Widget *) NULL, 9, "", 0);
830 define_label_data (whelp, (Widget *) NULL, 10, _("Quit"), quit_cmd,
831 whelp);
833 run_dlg (whelp);
834 interactive_display_finish ();
835 destroy_dlg (whelp);