Just a little correction at the it.po file.
[midnight-commander.git] / src / help.c
blob3c77d9bd88e1f56e02d620b497ff2bd35bd8a30a
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;
279 /* Create a replacement page with the error message */
280 return _(" Help file format error\n");
283 static char *select_next_link (char *start, char *current_link)
285 char *p;
287 if (!current_link)
288 return 0;
290 p = search_string_node (current_link, STRING_LINK_END);
291 if (!p)
292 return NULL;
293 p = search_string_node (p, STRING_LINK_START);
294 if (!p)
295 return NULL;
296 return p - 1;
299 static char *select_prev_link (char *start, char *current_link)
301 char *p;
303 if (!current_link)
304 return 0;
306 p = current_link - 1;
307 if (p <= start)
308 return 0;
310 p = search_char_node (p, CHAR_LINK_START, -1);
311 return p;
314 static void start_link_area (int x, int y, char *link_name)
316 Link_Area *new;
318 if (inside_link_area)
319 message (0, _("Warning"), _(" Internal bug: Double start of link area "));
321 /* Allocate memory for a new link area */
322 new = g_new (Link_Area, 1);
323 new->next = link_area;
324 link_area = new;
326 /* Save the beginning coordinates of the link area */
327 link_area->x1 = x;
328 link_area->y1 = y;
330 /* Save the name of the destination anchor */
331 link_area->link_name = link_name;
333 inside_link_area = 1;
336 static void end_link_area (int x, int y)
338 if (inside_link_area){
339 /* Save the end coordinates of the link area */
340 link_area->x2 = x;
341 link_area->y2 = y;
343 inside_link_area = 0;
347 static void clear_link_areas (void)
349 Link_Area *current;
351 while (link_area){
352 current = link_area;
353 link_area = current -> next;
354 g_free (current);
356 inside_link_area = 0;
359 static void help_show (Dlg_head *h, char *paint_start)
361 char *p;
362 int col, line, c;
363 int painting = 1;
364 int acs; /* Flag: Alternate character set active? */
365 int repeat_paint;
366 int active_col, active_line;/* Active link position */
368 attrset (HELP_NORMAL_COLOR);
369 do {
371 line = col = acs = active_col = active_line = repeat_paint = 0;
373 clear_link_areas ();
374 if (selected_item < paint_start)
375 selected_item = NULL;
377 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
378 c = (unsigned char)*p;
379 switch (c){
380 case CHAR_LINK_START:
381 if (selected_item == NULL)
382 selected_item = p;
383 if (p == selected_item){
384 attrset (HELP_SLINK_COLOR);
386 /* Store the coordinates of the link */
387 active_col = col + 2;
388 active_line = line + 2;
390 else
391 attrset (HELP_LINK_COLOR);
392 start_link_area (col, line, p);
393 break;
394 case CHAR_LINK_POINTER:
395 painting = 0;
396 end_link_area (col - 1, line);
397 break;
398 case CHAR_LINK_END:
399 painting = 1;
400 attrset (HELP_NORMAL_COLOR);
401 break;
402 case CHAR_ALTERNATE:
403 acs = 1;
404 break;
405 case CHAR_NORMAL:
406 acs = 0;
407 break;
408 case CHAR_VERSION:
409 dlg_move (h, line+2, col+2);
410 addstr (VERSION);
411 col += strlen (VERSION);
412 break;
413 case CHAR_FONT_BOLD:
414 attrset (HELP_BOLD_COLOR);
415 break;
416 case CHAR_FONT_ITALIC:
417 attrset (HELP_ITALIC_COLOR);
418 break;
419 case CHAR_FONT_NORMAL:
420 attrset (HELP_NORMAL_COLOR);
421 break;
422 case '\n':
423 line++;
424 col = 0;
425 break;
426 case '\t':
427 col = (col/8 + 1) * 8;
428 break;
429 default:
430 if (!painting)
431 continue;
432 if (col > HELP_WINDOW_WIDTH-1)
433 continue;
435 dlg_move (h, line+2, col+2);
436 if (acs){
437 if (c == ' ' || c == '.')
438 addch (c);
439 else
440 #ifndef NATIVE_WIN32
441 #ifndef HAVE_SLANG
442 addch (acs_map [c]);
443 #else
444 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
445 #endif
446 #else
447 addch (acs2pc (c));
448 #endif /* NATIVE_WIN32 */
449 } else
450 addch (c);
451 col++;
452 break;
455 last_shown = p;
456 end_of_node = line < help_lines;
457 attrset (HELP_NORMAL_COLOR);
458 if (selected_item >= last_shown){
459 if (link_area != NULL){
460 selected_item = link_area->link_name;
461 repeat_paint = 1;
463 else
464 selected_item = NULL;
466 } while (repeat_paint);
468 /* Position the cursor over a nice link */
469 if (active_col)
470 dlg_move (h, active_line, active_col);
473 static int help_event (Gpm_Event *event, Widget *w)
475 Link_Area *current_area;
477 if (! (event->type & GPM_UP))
478 return 0;
480 /* The event is relative to the dialog window, adjust it: */
481 event->y -= 2;
483 if (event->buttons & GPM_B_RIGHT){
484 currentpoint = startpoint = history [history_ptr].page;
485 selected_item = history [history_ptr].link;
486 history_ptr--;
487 if (history_ptr < 0)
488 history_ptr = HISTORY_SIZE-1;
490 help_callback (w->parent, 0, DLG_DRAW);
491 return 0;
494 /* Test whether the mouse click is inside one of the link areas */
495 current_area = link_area;
496 while (current_area)
498 /* Test one line link area */
499 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
500 event->y == current_area->y2 && event->x <= current_area->x2)
501 break;
502 /* Test two line link area */
503 if (current_area->y1 + 1 == current_area->y2){
504 /* The first line */
505 if (event->y == current_area->y1 && event->x >= current_area->x1)
506 break;
507 /* The second line */
508 if (event->y == current_area->y2 && event->x <= current_area->x2)
509 break;
511 /* Mouse will not work with link areas of more than two lines */
513 current_area = current_area -> next;
516 /* Test whether a link area was found */
517 if (current_area){
518 /* The click was inside a link area -> follow the link */
519 history_ptr = (history_ptr+1) % HISTORY_SIZE;
520 history [history_ptr].page = currentpoint;
521 history [history_ptr].link = current_area->link_name;
522 currentpoint = startpoint = help_follow_link (currentpoint, current_area->link_name);
523 selected_item = NULL;
524 } else{
525 if (event->y < 0)
526 move_backward (help_lines - 1);
527 else if (event->y >= help_lines)
528 move_forward (help_lines - 1);
529 else if (event->y < help_lines/2)
530 move_backward (1);
531 else
532 move_forward (1);
535 /* Show the new node */
536 help_callback (w->parent, 0, DLG_DRAW);
538 return 0;
541 /* show help */
542 static void
543 help_help_cmd (Dlg_head *h)
545 history_ptr = (history_ptr+1) % HISTORY_SIZE;
546 history [history_ptr].page = currentpoint;
547 history [history_ptr].link = selected_item;
548 currentpoint = startpoint = search_string (data, "[How to use help]") + 1;
549 selected_item = NULL;
550 help_callback (h, 0, DLG_DRAW);
553 static void
554 help_index_cmd (Dlg_head * h)
556 char *new_item;
558 if (!(new_item = search_string (data, "[Contents]"))) {
559 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
560 "[Contents]");
561 return;
564 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
565 history[history_ptr].page = currentpoint;
566 history[history_ptr].link = selected_item;
568 currentpoint = startpoint = new_item + 1;
569 selected_item = NULL;
570 help_callback (h, 0, DLG_DRAW);
573 static void help_quit_cmd (void *x)
575 dlg_stop ((Dlg_head *)x);
578 static void prev_node_cmd (Dlg_head *h)
580 currentpoint = startpoint = history [history_ptr].page;
581 selected_item = history [history_ptr].link;
582 history_ptr--;
583 if (history_ptr < 0)
584 history_ptr = HISTORY_SIZE-1;
586 help_callback (h, 0, DLG_DRAW);
589 static int md_callback (Widget *w, int msg, int par)
591 return default_proc (msg, par);
594 static Widget *mousedispatch_new (int y, int x, int yl, int xl)
596 Widget *w = g_new (Widget, 1);
598 init_widget (w, y, x, yl, xl,
599 (callback_fn) md_callback, 0, (mouse_h) help_event, NULL);
601 return w;
604 static int help_handle_key (struct Dlg_head *h, int c)
606 char *new_item;
608 if (c != KEY_UP && c != KEY_DOWN &&
609 check_movement_keys (c, 1, help_lines, currentpoint,
610 (movefn) move_backward2,
611 (movefn) move_forward2,
612 (movefn) move_to_top,
613 (movefn) move_to_bottom))
614 /* Nothing */;
615 else switch (c){
616 case 'l':
617 case KEY_LEFT:
618 prev_node_cmd (h);
619 break;
621 case '\n':
622 case KEY_RIGHT:
623 /* follow link */
624 if (!selected_item){
625 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
626 /* Is there any reason why the right key would take us
627 * backward if there are no links selected?, I agree
628 * with Torben than doing nothing in this case is better
630 /* If there are no links, go backward in history */
631 history_ptr--;
632 if (history_ptr < 0)
633 history_ptr = HISTORY_SIZE-1;
635 currentpoint = startpoint = history [history_ptr].page;
636 selected_item = history [history_ptr].link;
637 #endif
638 } else {
639 history_ptr = (history_ptr+1) % HISTORY_SIZE;
640 history [history_ptr].page = currentpoint;
641 history [history_ptr].link = selected_item;
642 currentpoint = startpoint = help_follow_link (currentpoint, selected_item) + 1;
644 selected_item = NULL;
645 break;
647 case KEY_DOWN:
648 case '\t':
649 /* select next link */
650 new_item = select_next_link (startpoint, selected_item);
651 if (new_item){
652 selected_item = new_item;
653 if (selected_item >= last_shown){
654 if (c == KEY_DOWN)
655 move_forward (1);
656 else
657 selected_item = NULL;
659 } else if (c == KEY_DOWN)
660 move_forward (1);
661 else
662 selected_item = NULL;
663 break;
665 case KEY_UP:
666 case ALT ('\t'):
667 /* select previous link */
668 new_item = select_prev_link (startpoint, selected_item);
669 selected_item = new_item;
670 if (selected_item < currentpoint || selected_item >= last_shown){
671 if (c == KEY_UP)
672 move_backward (1);
673 else{
674 if (link_area != NULL)
675 selected_item = link_area->link_name;
676 else
677 selected_item = NULL;
680 break;
682 case 'n':
683 /* Next node */
684 new_item = currentpoint;
685 while (*new_item && *new_item != CHAR_NODE_END)
686 new_item++;
687 if (*++new_item == '['){
688 while (*++new_item) {
689 if (*new_item == ']' && *++new_item && *++new_item) {
690 currentpoint = new_item;
691 selected_item = NULL;
692 break;
696 break;
698 case 'p':
699 /* Previous node */
700 new_item = currentpoint;
701 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
702 new_item--;
703 new_item--;
704 while (new_item > data && *new_item != CHAR_NODE_END)
705 new_item--;
706 while (*new_item != ']')
707 new_item++;
708 currentpoint = new_item + 2;
709 selected_item = NULL;
710 break;
712 case 'c':
713 help_index_cmd (h);
714 break;
716 case ESC_CHAR:
717 case XCTRL('g'):
718 dlg_stop (h);
719 break;
721 default:
722 return 0;
725 help_callback (h, 0, DLG_DRAW);
726 return 1;
729 static int help_callback (struct Dlg_head *h, int id, int msg)
731 switch (msg){
732 case DLG_DRAW:
733 common_dialog_repaint (h);
734 help_show (h, currentpoint);
735 break;
737 case DLG_KEY:
738 return help_handle_key (h, id);
740 return 0;
743 static void
744 interactive_display_finish (void)
746 clear_link_areas ();
747 g_free (data);
750 void
751 interactive_display (char *filename, char *node)
753 WButtonBar *help_bar;
754 Widget *md;
755 char *hlpfile = filename;
757 if (filename)
758 data = load_file (filename);
759 else
760 data = load_mc_home_file ("mc.hlp", &hlpfile);
762 if (data == NULL) {
763 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "), hlpfile,
764 unix_error_string (errno));
767 if (!filename)
768 g_free (hlpfile);
770 if (!data)
771 return;
773 if (!node || !*node)
774 node = "[main]";
776 if (!(main_node = search_string (data, node))) {
777 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
778 node);
780 /* Fallback to [main], return if it also cannot be found */
781 main_node = search_string (data, "[main]");
782 if (!main_node) {
783 interactive_display_finish ();
784 return;
788 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
790 whelp =
791 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
792 dialog_colors, help_callback, "[Help]", _("Help"),
793 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
795 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
796 currentpoint = startpoint = main_node + 1;
798 for (history_ptr = HISTORY_SIZE; history_ptr;) {
799 history_ptr--;
800 history[history_ptr].page = currentpoint;
801 history[history_ptr].link = selected_item;
804 help_bar = buttonbar_new (1);
805 help_bar->widget.y -= whelp->y;
806 help_bar->widget.x -= whelp->x;
808 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
810 add_widget (whelp, help_bar);
811 add_widget (whelp, md);
813 define_label_data (whelp, 1, _("Help"), (buttonbarfn) help_help_cmd,
814 whelp);
815 define_label_data (whelp, 2, _("Index"), (buttonbarfn) help_index_cmd,
816 whelp);
817 define_label_data (whelp, 3, _("Prev"), (buttonbarfn) prev_node_cmd,
818 whelp);
819 define_label (whelp, 4, "", 0);
820 define_label (whelp, 5, "", 0);
821 define_label (whelp, 6, "", 0);
822 define_label (whelp, 7, "", 0);
823 define_label (whelp, 8, "", 0);
824 define_label (whelp, 9, "", 0);
825 define_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
827 run_dlg (whelp);
828 interactive_display_finish ();
829 destroy_dlg (whelp);