Replaced 6 with & as & where meant
[midnight-commander.git] / src / help.c
blob3261cbbb351858c02fb342264357caf5543983f4
1 /* Hypertext file browser.
2 Copyright (C) 1994, 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
3 2005, 2006, 2007 Free Software Foundation, Inc.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 Implements the hypertext file viewer.
22 The hypertext file is a file that may have one or more nodes. Each
23 node ends with a ^D character and starts with a bracket, then the
24 name of the node and then a closing bracket. Right after the closing
25 bracket a newline is placed. This newline is not to be displayed by
26 the help viewer and must be skipped - its sole purpose is to faciliate
27 the work of the people managing the help file template (xnc.hlp) .
29 Links in the hypertext file are specified like this: the text that
30 will be highlighted should have a leading ^A, then it comes the
31 text, then a ^B indicating that highlighting is done, then the name
32 of the node you want to link to and then a ^C.
34 The file must contain a ^D at the beginning and at the end of the
35 file or the program will not be able to detect the end of file.
37 Lazyness/widgeting attack: This file does use the dialog manager
38 and uses mainly the dialog to achieve the help work. there is only
39 one specialized widget and it's only used to forward the mouse messages
40 to the appropiate routine.
44 #include <config.h>
46 #include <errno.h>
47 #include <stdio.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
52 #include "global.h"
53 #include "tty.h"
54 #include "color.h"
55 #include "win.h"
56 #include "mouse.h"
57 #include "key.h" /* For mi_getch() */
58 #include "help.h"
59 #include "dialog.h" /* For Dlg_head */
60 #include "widget.h" /* For Widget */
61 #include "wtools.h" /* For common_dialog_repaint() */
63 #define MAXLINKNAME 80
64 #define HISTORY_SIZE 20
65 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
67 #define STRING_LINK_START "\01"
68 #define STRING_LINK_POINTER "\02"
69 #define STRING_LINK_END "\03"
70 #define STRING_NODE_END "\04"
72 static char *data; /* Pointer to the loaded data file */
73 static int help_lines; /* Lines in help viewer */
74 static int history_ptr; /* For the history queue */
75 static const char *main_node; /* The main node */
76 static const char *last_shown = NULL; /* Last byte shown in a screen */
77 static int end_of_node = 0; /* Flag: the last character of the node shown? */
78 static const char *currentpoint;
79 static const char *selected_item;
81 /* The widget variables */
82 static Dlg_head *whelp;
84 static struct {
85 const char *page; /* Pointer to the selected page */
86 const char *link; /* Pointer to the selected link */
87 } history [HISTORY_SIZE];
89 /* Link areas for the mouse */
90 typedef struct Link_Area {
91 int x1, y1, x2, y2;
92 const char *link_name;
93 struct Link_Area *next;
94 } Link_Area;
96 static Link_Area *link_area = NULL;
97 static int inside_link_area = 0;
99 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
101 /* returns the position where text was found in the start buffer */
102 /* or 0 if not found */
103 static const char *
104 search_string (const char *start, const char *text)
106 const char *result = NULL;
107 char *local_text = g_strdup (text);
108 char *d = local_text;
109 const char *e = start;
111 /* fmt sometimes replaces a space with a newline in the help file */
112 /* Replace the newlines in the link name with spaces to correct the situation */
113 while (*d){
114 if (*d == '\n')
115 *d = ' ';
116 d++;
118 /* Do search */
119 for (d = local_text; *e; e++){
120 if (*d == *e)
121 d++;
122 else
123 d = local_text;
124 if (!*d) {
125 result = e + 1;
126 goto cleanup;
129 cleanup:
130 g_free (local_text);
131 return result;
134 /* Searches text in the buffer pointed by start. Search ends */
135 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
136 static const char *search_string_node (const char *start, const char *text)
138 const char *d = text;
139 const char *e = start;
141 if (!start)
142 return 0;
144 for (; *e && *e != CHAR_NODE_END; e++){
145 if (*d == *e)
146 d++;
147 else
148 d = text;
149 if (!*d)
150 return e+1;
152 return 0;
155 /* Searches the_char in the buffer pointer by start and searches */
156 /* it can search forward (direction = 1) or backward (direction = -1) */
157 static const char *search_char_node (const char *start, char the_char, int direction)
159 const char *e;
161 e = start;
163 for (; *e && (*e != CHAR_NODE_END); e += direction){
164 if (*e == the_char)
165 return e;
167 return 0;
170 /* Returns the new current pointer when moved lines lines */
171 static const char *move_forward2 (const char *c, int lines)
173 const char *p;
174 int line;
176 currentpoint = c;
177 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
178 if (line == lines)
179 return currentpoint = p;
180 if (*p == '\n')
181 line++;
183 return currentpoint = c;
186 static const char *move_backward2 (const char *c, int lines)
188 const char *p;
189 int line;
191 currentpoint = c;
192 for (line = 0, p = currentpoint; *p && p >= data; p--){
193 if (*p == CHAR_NODE_END)
195 /* We reached the beginning of the node */
196 /* Skip the node headers */
197 while (*p != ']') p++;
198 return currentpoint = p + 2; /* Skip the newline following the start of the node */
200 if (*(p - 1) == '\n')
201 line++;
202 if (line == lines)
203 return currentpoint = p;
205 return currentpoint = c;
208 static void move_forward (int i)
210 if (end_of_node)
211 return;
212 currentpoint = move_forward2 (currentpoint, i);
215 static void move_backward (int i)
217 currentpoint = move_backward2 (currentpoint, ++i);
220 static void move_to_top (void)
222 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
223 currentpoint--;
224 while (*currentpoint != ']')
225 currentpoint++;
226 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
227 selected_item = NULL;
230 static void move_to_bottom (void)
232 while (*currentpoint && *currentpoint != CHAR_NODE_END)
233 currentpoint++;
234 currentpoint--;
235 move_backward (help_lines - 1);
238 static const char *help_follow_link (const char *start, const char *selected_item)
240 char link_name [MAXLINKNAME];
241 const char *p;
242 int i = 0;
244 if (!selected_item)
245 return start;
247 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
249 if (*p == CHAR_LINK_POINTER){
250 link_name [0] = '[';
251 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
252 link_name [i++] = *++p;
253 link_name [i-1] = ']';
254 link_name [i] = 0;
255 p = search_string (data, link_name);
256 if (p) {
257 p += 1; /* Skip the newline following the start of the node */
258 return p;
262 /* Create a replacement page with the error message */
263 return _(" Help file format error\n");
266 static const char *select_next_link (const char *current_link)
268 const char *p;
270 if (!current_link)
271 return 0;
273 p = search_string_node (current_link, STRING_LINK_END);
274 if (!p)
275 return NULL;
276 p = search_string_node (p, STRING_LINK_START);
277 if (!p)
278 return NULL;
279 return p - 1;
282 static const char *select_prev_link (const char *current_link)
284 if (!current_link)
285 return 0;
287 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
290 static void start_link_area (int x, int y, const char *link_name)
292 Link_Area *new;
294 if (inside_link_area)
295 message (0, _("Warning"), _(" Internal bug: Double start of link area "));
297 /* Allocate memory for a new link area */
298 new = g_new (Link_Area, 1);
299 new->next = link_area;
300 link_area = new;
302 /* Save the beginning coordinates of the link area */
303 link_area->x1 = x;
304 link_area->y1 = y;
306 /* Save the name of the destination anchor */
307 link_area->link_name = link_name;
309 inside_link_area = 1;
312 static void end_link_area (int x, int y)
314 if (inside_link_area){
315 /* Save the end coordinates of the link area */
316 link_area->x2 = x;
317 link_area->y2 = y;
319 inside_link_area = 0;
323 static void clear_link_areas (void)
325 Link_Area *current;
327 while (link_area){
328 current = link_area;
329 link_area = current -> next;
330 g_free (current);
332 inside_link_area = 0;
335 static void help_show (Dlg_head *h, const char *paint_start)
337 const char *p;
338 int col, line, c;
339 int painting = 1;
340 int acs; /* Flag: Alternate character set active? */
341 int repeat_paint;
342 int active_col, active_line;/* Active link position */
344 attrset (HELP_NORMAL_COLOR);
345 do {
347 line = col = acs = active_col = active_line = repeat_paint = 0;
349 clear_link_areas ();
350 if (selected_item < paint_start)
351 selected_item = NULL;
353 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
354 c = (unsigned char)*p;
355 switch (c){
356 case CHAR_LINK_START:
357 if (selected_item == NULL)
358 selected_item = p;
359 if (p == selected_item){
360 attrset (HELP_SLINK_COLOR);
362 /* Store the coordinates of the link */
363 active_col = col + 2;
364 active_line = line + 2;
366 else
367 attrset (HELP_LINK_COLOR);
368 start_link_area (col, line, p);
369 break;
370 case CHAR_LINK_POINTER:
371 painting = 0;
372 end_link_area (col - 1, line);
373 break;
374 case CHAR_LINK_END:
375 painting = 1;
376 attrset (HELP_NORMAL_COLOR);
377 break;
378 case CHAR_ALTERNATE:
379 acs = 1;
380 break;
381 case CHAR_NORMAL:
382 acs = 0;
383 break;
384 case CHAR_VERSION:
385 dlg_move (h, line+2, col+2);
386 addstr (VERSION);
387 col += strlen (VERSION);
388 break;
389 case CHAR_FONT_BOLD:
390 attrset (HELP_BOLD_COLOR);
391 break;
392 case CHAR_FONT_ITALIC:
393 attrset (HELP_ITALIC_COLOR);
394 break;
395 case CHAR_FONT_NORMAL:
396 attrset (HELP_NORMAL_COLOR);
397 break;
398 case '\n':
399 line++;
400 col = 0;
401 break;
402 case '\t':
403 col = (col/8 + 1) * 8;
404 break;
405 default:
406 if (!painting)
407 continue;
408 if (col > HELP_WINDOW_WIDTH-1)
409 continue;
411 dlg_move (h, line+2, col+2);
412 if (acs){
413 if (c == ' ' || c == '.')
414 addch (c);
415 else
416 #ifndef HAVE_SLANG
417 addch (acs_map [c]);
418 #else
419 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
420 #endif
421 } else
422 addch (c);
423 col++;
424 break;
427 last_shown = p;
428 end_of_node = line < help_lines;
429 attrset (HELP_NORMAL_COLOR);
430 if (selected_item >= last_shown){
431 if (link_area != NULL){
432 selected_item = link_area->link_name;
433 repeat_paint = 1;
435 else
436 selected_item = NULL;
438 } while (repeat_paint);
440 /* Position the cursor over a nice link */
441 if (active_col)
442 dlg_move (h, active_line, active_col);
445 static int
446 help_event (Gpm_Event *event, void *vp)
448 Widget *w = vp;
449 Link_Area *current_area;
451 if (! (event->type & GPM_UP))
452 return 0;
454 /* The event is relative to the dialog window, adjust it: */
455 event->x -= 2;
456 event->y -= 2;
458 if (event->buttons & GPM_B_RIGHT){
459 currentpoint = history [history_ptr].page;
460 selected_item = history [history_ptr].link;
461 history_ptr--;
462 if (history_ptr < 0)
463 history_ptr = HISTORY_SIZE-1;
465 help_callback (w->parent, DLG_DRAW, 0);
466 return 0;
469 /* Test whether the mouse click is inside one of the link areas */
470 current_area = link_area;
471 while (current_area)
473 /* Test one line link area */
474 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
475 event->y == current_area->y2 && event->x <= current_area->x2)
476 break;
477 /* Test two line link area */
478 if (current_area->y1 + 1 == current_area->y2){
479 /* The first line */
480 if (event->y == current_area->y1 && event->x >= current_area->x1)
481 break;
482 /* The second line */
483 if (event->y == current_area->y2 && event->x <= current_area->x2)
484 break;
486 /* Mouse will not work with link areas of more than two lines */
488 current_area = current_area -> next;
491 /* Test whether a link area was found */
492 if (current_area){
493 /* The click was inside a link area -> follow the link */
494 history_ptr = (history_ptr+1) % HISTORY_SIZE;
495 history [history_ptr].page = currentpoint;
496 history [history_ptr].link = current_area->link_name;
497 currentpoint = help_follow_link (currentpoint, current_area->link_name);
498 selected_item = NULL;
499 } else{
500 if (event->y < 0)
501 move_backward (help_lines - 1);
502 else if (event->y >= help_lines)
503 move_forward (help_lines - 1);
504 else if (event->y < help_lines/2)
505 move_backward (1);
506 else
507 move_forward (1);
510 /* Show the new node */
511 help_callback (w->parent, DLG_DRAW, 0);
513 return 0;
516 /* show help */
517 static void
518 help_help_cmd (void *vp)
520 Dlg_head *h = vp;
521 const char *p;
523 history_ptr = (history_ptr+1) % HISTORY_SIZE;
524 history [history_ptr].page = currentpoint;
525 history [history_ptr].link = selected_item;
527 p = search_string(data, "[How to use help]");
528 if (p == NULL)
529 return;
531 currentpoint = p + 1; /* Skip the newline following the start of the node */
532 selected_item = NULL;
533 help_callback (h, DLG_DRAW, 0);
536 static void
537 help_index_cmd (void *vp)
539 Dlg_head *h = vp;
540 const char *new_item;
542 if (!(new_item = search_string (data, "[Contents]"))) {
543 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
544 "[Contents]");
545 return;
548 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
549 history[history_ptr].page = currentpoint;
550 history[history_ptr].link = selected_item;
552 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
553 selected_item = NULL;
554 help_callback (h, DLG_DRAW, 0);
557 static void help_quit_cmd (void *vp)
559 dlg_stop ((Dlg_head *) vp);
562 static void prev_node_cmd (void *vp)
564 Dlg_head *h = vp;
565 currentpoint = history [history_ptr].page;
566 selected_item = history [history_ptr].link;
567 history_ptr--;
568 if (history_ptr < 0)
569 history_ptr = HISTORY_SIZE-1;
571 help_callback (h, DLG_DRAW, 0);
574 static cb_ret_t
575 md_callback (Widget *w, widget_msg_t msg, int parm)
577 (void) w;
578 return default_proc (msg, parm);
581 static Widget *
582 mousedispatch_new (int y, int x, int yl, int xl)
584 Widget *w = g_new (Widget, 1);
586 init_widget (w, y, x, yl, xl, md_callback, help_event);
588 return w;
591 static void help_cmk_move_backward(void *vp, int lines) {
592 (void) &vp;
593 move_backward(lines);
595 static void help_cmk_move_forward(void *vp, int lines) {
596 (void) &vp;
597 move_forward(lines);
599 static void help_cmk_moveto_top(void *vp, int lines) {
600 (void) &vp;
601 (void) &lines;
602 move_to_top();
604 static void help_cmk_moveto_bottom(void *vp, int lines) {
605 (void) &vp;
606 (void) &lines;
607 move_to_bottom();
610 static cb_ret_t
611 help_handle_key (struct Dlg_head *h, int c)
613 const char *new_item;
615 if (c != KEY_UP && c != KEY_DOWN &&
616 check_movement_keys (c, help_lines, NULL,
617 help_cmk_move_backward,
618 help_cmk_move_forward,
619 help_cmk_moveto_top,
620 help_cmk_moveto_bottom)) {
621 /* Nothing */;
622 } else switch (c){
623 case 'l':
624 case KEY_LEFT:
625 prev_node_cmd (h);
626 break;
628 case '\n':
629 case KEY_RIGHT:
630 /* follow link */
631 if (!selected_item){
632 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
633 /* Is there any reason why the right key would take us
634 * backward if there are no links selected?, I agree
635 * with Torben than doing nothing in this case is better
637 /* If there are no links, go backward in history */
638 history_ptr--;
639 if (history_ptr < 0)
640 history_ptr = HISTORY_SIZE-1;
642 currentpoint = history [history_ptr].page;
643 selected_item = history [history_ptr].link;
644 #endif
645 } else {
646 history_ptr = (history_ptr+1) % HISTORY_SIZE;
647 history [history_ptr].page = currentpoint;
648 history [history_ptr].link = selected_item;
649 currentpoint = help_follow_link (currentpoint, selected_item);
651 selected_item = NULL;
652 break;
654 case KEY_DOWN:
655 case '\t':
656 new_item = select_next_link (selected_item);
657 if (new_item){
658 selected_item = new_item;
659 if (selected_item >= last_shown){
660 if (c == KEY_DOWN)
661 move_forward (1);
662 else
663 selected_item = NULL;
665 } else if (c == KEY_DOWN)
666 move_forward (1);
667 else
668 selected_item = NULL;
669 break;
671 case KEY_UP:
672 case ALT ('\t'):
673 /* select previous link */
674 new_item = select_prev_link (selected_item);
675 selected_item = new_item;
676 if (selected_item == NULL || selected_item < currentpoint) {
677 if (c == KEY_UP)
678 move_backward (1);
679 else{
680 if (link_area != NULL)
681 selected_item = link_area->link_name;
682 else
683 selected_item = NULL;
686 break;
688 case 'n':
689 /* Next node */
690 new_item = currentpoint;
691 while (*new_item && *new_item != CHAR_NODE_END)
692 new_item++;
693 if (*++new_item == '['){
694 while (*++new_item) {
695 if (*new_item == ']' && *++new_item && *++new_item) {
696 currentpoint = new_item;
697 selected_item = NULL;
698 break;
702 break;
704 case 'p':
705 /* Previous node */
706 new_item = currentpoint;
707 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
708 new_item--;
709 new_item--;
710 while (new_item > data && *new_item != CHAR_NODE_END)
711 new_item--;
712 while (*new_item != ']')
713 new_item++;
714 currentpoint = new_item + 2;
715 selected_item = NULL;
716 break;
718 case 'c':
719 help_index_cmd (h);
720 break;
722 case ESC_CHAR:
723 case XCTRL('g'):
724 dlg_stop (h);
725 break;
727 default:
728 return MSG_NOT_HANDLED;
731 help_callback (h, DLG_DRAW, 0);
732 return MSG_HANDLED;
735 static cb_ret_t
736 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
738 switch (msg) {
739 case DLG_DRAW:
740 common_dialog_repaint (h);
741 help_show (h, currentpoint);
742 return MSG_HANDLED;
744 case DLG_KEY:
745 return help_handle_key (h, parm);
747 default:
748 return default_dlg_callback (h, msg, parm);
752 static void
753 interactive_display_finish (void)
755 clear_link_areas ();
756 g_free (data);
759 void
760 interactive_display (const char *filename, const char *node)
762 WButtonBar *help_bar;
763 Widget *md;
764 char *hlpfile = NULL;
766 if (filename)
767 data = load_file (filename);
768 else
769 data = load_mc_home_file ("mc.hlp", &hlpfile);
771 if (data == NULL) {
772 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
773 unix_error_string (errno));
776 if (!filename)
777 g_free (hlpfile);
779 if (!data)
780 return;
782 if (!node || !*node)
783 node = "[main]";
785 if (!(main_node = search_string (data, node))) {
786 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
787 node);
789 /* Fallback to [main], return if it also cannot be found */
790 main_node = search_string (data, "[main]");
791 if (!main_node) {
792 interactive_display_finish ();
793 return;
797 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
799 whelp =
800 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
801 dialog_colors, help_callback, "[Help]", _("Help"),
802 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
804 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
805 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
807 for (history_ptr = HISTORY_SIZE; history_ptr;) {
808 history_ptr--;
809 history[history_ptr].page = currentpoint;
810 history[history_ptr].link = selected_item;
813 help_bar = buttonbar_new (1);
814 ((Widget *) help_bar)->y -= whelp->y;
815 ((Widget *) help_bar)->x -= whelp->x;
817 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
819 add_widget (whelp, md);
820 add_widget (whelp, help_bar);
822 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
823 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
824 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
825 buttonbar_clear_label (whelp, 4);
826 buttonbar_clear_label (whelp, 5);
827 buttonbar_clear_label (whelp, 6);
828 buttonbar_clear_label (whelp, 7);
829 buttonbar_clear_label (whelp, 8);
830 buttonbar_clear_label (whelp, 9);
831 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
833 run_dlg (whelp);
834 interactive_display_finish ();
835 destroy_dlg (whelp);