Removed type SHELL_ESCAPED_STR in favour of plain char*
[midnight-commander.git] / src / help.c
blobb06c1889900aacf939ea6ff4f3a460868344cd67
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>
48 #include <sys/types.h>
49 #include <sys/stat.h>
51 #include <mhl/memory.h>
52 #include <mhl/string.h>
54 #include "global.h"
55 #include "tty.h"
56 #include "color.h"
57 #include "win.h"
58 #include "mouse.h"
59 #include "key.h" /* For mi_getch() */
60 #include "help.h"
61 #include "dialog.h" /* For Dlg_head */
62 #include "widget.h" /* For Widget */
63 #include "wtools.h" /* For common_dialog_repaint() */
65 #define MAXLINKNAME 80
66 #define HISTORY_SIZE 20
67 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
69 #define STRING_LINK_START "\01"
70 #define STRING_LINK_POINTER "\02"
71 #define STRING_LINK_END "\03"
72 #define STRING_NODE_END "\04"
74 static char *data; /* Pointer to the loaded data file */
75 static int help_lines; /* Lines in help viewer */
76 static int history_ptr; /* For the history queue */
77 static const char *main_node; /* The main node */
78 static const char *last_shown = NULL; /* Last byte shown in a screen */
79 static int end_of_node = 0; /* Flag: the last character of the node shown? */
80 static const char *currentpoint;
81 static const char *selected_item;
83 /* The widget variables */
84 static Dlg_head *whelp;
86 static struct {
87 const char *page; /* Pointer to the selected page */
88 const char *link; /* Pointer to the selected link */
89 } history [HISTORY_SIZE];
91 /* Link areas for the mouse */
92 typedef struct Link_Area {
93 int x1, y1, x2, y2;
94 const char *link_name;
95 struct Link_Area *next;
96 } Link_Area;
98 static Link_Area *link_area = NULL;
99 static int inside_link_area = 0;
101 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
103 /* returns the position where text was found in the start buffer */
104 /* or 0 if not found */
105 static const char *
106 search_string (const char *start, const char *text)
108 const char *result = NULL;
109 char *local_text = g_strdup (text);
110 char *d = local_text;
111 const char *e = start;
113 /* fmt sometimes replaces a space with a newline in the help file */
114 /* Replace the newlines in the link name with spaces to correct the situation */
115 while (*d){
116 if (*d == '\n')
117 *d = ' ';
118 d++;
120 /* Do search */
121 for (d = local_text; *e; e++){
122 if (*d == *e)
123 d++;
124 else
125 d = local_text;
126 if (!*d) {
127 result = e + 1;
128 goto cleanup;
131 cleanup:
132 g_free (local_text);
133 return result;
136 /* Searches text in the buffer pointed by start. Search ends */
137 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
138 static const char *search_string_node (const char *start, const char *text)
140 const char *d = text;
141 const char *e = start;
143 if (!start)
144 return 0;
146 for (; *e && *e != CHAR_NODE_END; e++){
147 if (*d == *e)
148 d++;
149 else
150 d = text;
151 if (!*d)
152 return e+1;
154 return 0;
157 /* Searches the_char in the buffer pointer by start and searches */
158 /* it can search forward (direction = 1) or backward (direction = -1) */
159 static const char *search_char_node (const char *start, char the_char, int direction)
161 const char *e;
163 e = start;
165 for (; *e && (*e != CHAR_NODE_END); e += direction){
166 if (*e == the_char)
167 return e;
169 return 0;
172 /* Returns the new current pointer when moved lines lines */
173 static const char *move_forward2 (const char *c, int lines)
175 const char *p;
176 int line;
178 currentpoint = c;
179 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
180 if (line == lines)
181 return currentpoint = p;
182 if (*p == '\n')
183 line++;
185 return currentpoint = c;
188 static const char *move_backward2 (const char *c, int lines)
190 const char *p;
191 int line;
193 currentpoint = c;
194 for (line = 0, p = currentpoint; *p && p >= data; p--){
195 if (*p == CHAR_NODE_END)
197 /* We reached the beginning of the node */
198 /* Skip the node headers */
199 while (*p != ']') p++;
200 return currentpoint = p + 2; /* Skip the newline following the start of the node */
202 if (*(p - 1) == '\n')
203 line++;
204 if (line == lines)
205 return currentpoint = p;
207 return currentpoint = c;
210 static void move_forward (int i)
212 if (end_of_node)
213 return;
214 currentpoint = move_forward2 (currentpoint, i);
217 static void move_backward (int i)
219 currentpoint = move_backward2 (currentpoint, ++i);
222 static void move_to_top (void)
224 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
225 currentpoint--;
226 while (*currentpoint != ']')
227 currentpoint++;
228 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
229 selected_item = NULL;
232 static void move_to_bottom (void)
234 while (*currentpoint && *currentpoint != CHAR_NODE_END)
235 currentpoint++;
236 currentpoint--;
237 move_backward (help_lines - 1);
240 static const char *help_follow_link (const char *start, const char *selected_item)
242 char link_name [MAXLINKNAME];
243 const char *p;
244 int i = 0;
246 if (!selected_item)
247 return start;
249 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
251 if (*p == CHAR_LINK_POINTER){
252 link_name [0] = '[';
253 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
254 link_name [i++] = *++p;
255 link_name [i-1] = ']';
256 link_name [i] = 0;
257 p = search_string (data, link_name);
258 if (p) {
259 p += 1; /* Skip the newline following the start of the node */
260 return p;
264 /* Create a replacement page with the error message */
265 return _(" Help file format error\n");
268 static const char *select_next_link (const char *current_link)
270 const char *p;
272 if (!current_link)
273 return 0;
275 p = search_string_node (current_link, STRING_LINK_END);
276 if (!p)
277 return NULL;
278 p = search_string_node (p, STRING_LINK_START);
279 if (!p)
280 return NULL;
281 return p - 1;
284 static const char *select_prev_link (const char *current_link)
286 if (!current_link)
287 return 0;
289 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
292 static void start_link_area (int x, int y, const char *link_name)
294 Link_Area *new;
296 if (inside_link_area)
297 message (D_NORMAL, _("Warning"), _(" Internal bug: Double start of link area "));
299 /* Allocate memory for a new link area */
300 new = g_new (Link_Area, 1);
301 new->next = link_area;
302 link_area = new;
304 /* Save the beginning coordinates of the link area */
305 link_area->x1 = x;
306 link_area->y1 = y;
308 /* Save the name of the destination anchor */
309 link_area->link_name = link_name;
311 inside_link_area = 1;
314 static void end_link_area (int x, int y)
316 if (inside_link_area){
317 /* Save the end coordinates of the link area */
318 link_area->x2 = x;
319 link_area->y2 = y;
321 inside_link_area = 0;
325 static void clear_link_areas (void)
327 Link_Area *current;
329 while (link_area){
330 current = link_area;
331 link_area = current -> next;
332 g_free (current);
334 inside_link_area = 0;
337 static void help_show (Dlg_head *h, const char *paint_start)
339 const char *p;
340 int col, line, c;
341 int painting = 1;
342 int acs; /* Flag: Alternate character set active? */
343 int repeat_paint;
344 int active_col, active_line;/* Active link position */
346 attrset (HELP_NORMAL_COLOR);
347 do {
349 line = col = acs = active_col = active_line = repeat_paint = 0;
351 clear_link_areas ();
352 if (selected_item < paint_start)
353 selected_item = NULL;
355 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
356 c = (unsigned char)*p;
357 switch (c){
358 case CHAR_LINK_START:
359 if (selected_item == NULL)
360 selected_item = p;
361 if (p == selected_item){
362 attrset (HELP_SLINK_COLOR);
364 /* Store the coordinates of the link */
365 active_col = col + 2;
366 active_line = line + 2;
368 else
369 attrset (HELP_LINK_COLOR);
370 start_link_area (col, line, p);
371 break;
372 case CHAR_LINK_POINTER:
373 painting = 0;
374 end_link_area (col - 1, line);
375 break;
376 case CHAR_LINK_END:
377 painting = 1;
378 attrset (HELP_NORMAL_COLOR);
379 break;
380 case CHAR_ALTERNATE:
381 acs = 1;
382 break;
383 case CHAR_NORMAL:
384 acs = 0;
385 break;
386 case CHAR_VERSION:
387 dlg_move (h, line+2, col+2);
388 addstr (VERSION);
389 col += strlen (VERSION);
390 break;
391 case CHAR_FONT_BOLD:
392 attrset (HELP_BOLD_COLOR);
393 break;
394 case CHAR_FONT_ITALIC:
395 attrset (HELP_ITALIC_COLOR);
396 break;
397 case CHAR_FONT_NORMAL:
398 attrset (HELP_NORMAL_COLOR);
399 break;
400 case '\n':
401 line++;
402 col = 0;
403 break;
404 case '\t':
405 col = (col/8 + 1) * 8;
406 break;
407 default:
408 if (!painting)
409 continue;
410 if (col > HELP_WINDOW_WIDTH-1)
411 continue;
413 dlg_move (h, line+2, col+2);
414 if (acs){
415 if (c == ' ' || c == '.')
416 addch (c);
417 else
418 #ifndef HAVE_SLANG
419 addch (acs_map [c]);
420 #else
421 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
422 #endif
423 } else
424 addch (c);
425 col++;
426 break;
429 last_shown = p;
430 end_of_node = line < help_lines;
431 attrset (HELP_NORMAL_COLOR);
432 if (selected_item >= last_shown){
433 if (link_area != NULL){
434 selected_item = link_area->link_name;
435 repeat_paint = 1;
437 else
438 selected_item = NULL;
440 } while (repeat_paint);
442 /* Position the cursor over a nice link */
443 if (active_col)
444 dlg_move (h, active_line, active_col);
447 static int
448 help_event (Gpm_Event *event, void *vp)
450 Widget *w = vp;
451 Link_Area *current_area;
453 if (! (event->type & GPM_UP))
454 return 0;
456 /* The event is relative to the dialog window, adjust it: */
457 event->x -= 2;
458 event->y -= 2;
460 if (event->buttons & GPM_B_RIGHT){
461 currentpoint = history [history_ptr].page;
462 selected_item = history [history_ptr].link;
463 history_ptr--;
464 if (history_ptr < 0)
465 history_ptr = HISTORY_SIZE-1;
467 help_callback (w->parent, DLG_DRAW, 0);
468 return 0;
471 /* Test whether the mouse click is inside one of the link areas */
472 current_area = link_area;
473 while (current_area)
475 /* Test one line link area */
476 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
477 event->y == current_area->y2 && event->x <= current_area->x2)
478 break;
479 /* Test two line link area */
480 if (current_area->y1 + 1 == current_area->y2){
481 /* The first line */
482 if (event->y == current_area->y1 && event->x >= current_area->x1)
483 break;
484 /* The second line */
485 if (event->y == current_area->y2 && event->x <= current_area->x2)
486 break;
488 /* Mouse will not work with link areas of more than two lines */
490 current_area = current_area -> next;
493 /* Test whether a link area was found */
494 if (current_area){
495 /* The click was inside a link area -> follow the link */
496 history_ptr = (history_ptr+1) % HISTORY_SIZE;
497 history [history_ptr].page = currentpoint;
498 history [history_ptr].link = current_area->link_name;
499 currentpoint = help_follow_link (currentpoint, current_area->link_name);
500 selected_item = NULL;
501 } else{
502 if (event->y < 0)
503 move_backward (help_lines - 1);
504 else if (event->y >= help_lines)
505 move_forward (help_lines - 1);
506 else if (event->y < help_lines/2)
507 move_backward (1);
508 else
509 move_forward (1);
512 /* Show the new node */
513 help_callback (w->parent, DLG_DRAW, 0);
515 return 0;
518 /* show help */
519 static void
520 help_help_cmd (void *vp)
522 Dlg_head *h = vp;
523 const char *p;
525 history_ptr = (history_ptr+1) % HISTORY_SIZE;
526 history [history_ptr].page = currentpoint;
527 history [history_ptr].link = selected_item;
529 p = search_string(data, "[How to use help]");
530 if (p == NULL)
531 return;
533 currentpoint = p + 1; /* Skip the newline following the start of the node */
534 selected_item = NULL;
535 help_callback (h, DLG_DRAW, 0);
538 static void
539 help_index_cmd (void *vp)
541 Dlg_head *h = vp;
542 const char *new_item;
544 if (!(new_item = search_string (data, "[Contents]"))) {
545 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
546 "[Contents]");
547 return;
550 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
551 history[history_ptr].page = currentpoint;
552 history[history_ptr].link = selected_item;
554 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
555 selected_item = NULL;
556 help_callback (h, DLG_DRAW, 0);
559 static void help_quit_cmd (void *vp)
561 dlg_stop ((Dlg_head *) vp);
564 static void prev_node_cmd (void *vp)
566 Dlg_head *h = vp;
567 currentpoint = history [history_ptr].page;
568 selected_item = history [history_ptr].link;
569 history_ptr--;
570 if (history_ptr < 0)
571 history_ptr = HISTORY_SIZE-1;
573 help_callback (h, DLG_DRAW, 0);
576 static cb_ret_t
577 md_callback (Widget *w, widget_msg_t msg, int parm)
579 (void) w;
580 return default_proc (msg, parm);
583 static Widget *
584 mousedispatch_new (int y, int x, int yl, int xl)
586 Widget *w = g_new (Widget, 1);
588 init_widget (w, y, x, yl, xl, md_callback, help_event);
590 return w;
593 static void help_cmk_move_backward(void *vp, int lines) {
594 (void) &vp;
595 move_backward(lines);
597 static void help_cmk_move_forward(void *vp, int lines) {
598 (void) &vp;
599 move_forward(lines);
601 static void help_cmk_moveto_top(void *vp, int lines) {
602 (void) &vp;
603 (void) &lines;
604 move_to_top();
606 static void help_cmk_moveto_bottom(void *vp, int lines) {
607 (void) &vp;
608 (void) &lines;
609 move_to_bottom();
612 static cb_ret_t
613 help_handle_key (struct Dlg_head *h, int c)
615 const char *new_item;
617 if (c != KEY_UP && c != KEY_DOWN &&
618 check_movement_keys (c, help_lines, NULL,
619 help_cmk_move_backward,
620 help_cmk_move_forward,
621 help_cmk_moveto_top,
622 help_cmk_moveto_bottom)) {
623 /* Nothing */;
624 } else switch (c){
625 case 'l':
626 case KEY_LEFT:
627 prev_node_cmd (h);
628 break;
630 case '\n':
631 case KEY_RIGHT:
632 /* follow link */
633 if (!selected_item){
634 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
635 /* Is there any reason why the right key would take us
636 * backward if there are no links selected?, I agree
637 * with Torben than doing nothing in this case is better
639 /* If there are no links, go backward in history */
640 history_ptr--;
641 if (history_ptr < 0)
642 history_ptr = HISTORY_SIZE-1;
644 currentpoint = history [history_ptr].page;
645 selected_item = history [history_ptr].link;
646 #endif
647 } else {
648 history_ptr = (history_ptr+1) % HISTORY_SIZE;
649 history [history_ptr].page = currentpoint;
650 history [history_ptr].link = selected_item;
651 currentpoint = help_follow_link (currentpoint, selected_item);
653 selected_item = NULL;
654 break;
656 case KEY_DOWN:
657 case '\t':
658 new_item = select_next_link (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 (selected_item);
677 selected_item = new_item;
678 if (selected_item == NULL || selected_item < currentpoint) {
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 MSG_NOT_HANDLED;
733 help_callback (h, DLG_DRAW, 0);
734 return MSG_HANDLED;
737 static cb_ret_t
738 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
740 switch (msg) {
741 case DLG_DRAW:
742 common_dialog_repaint (h);
743 help_show (h, currentpoint);
744 return MSG_HANDLED;
746 case DLG_KEY:
747 return help_handle_key (h, parm);
749 default:
750 return default_dlg_callback (h, msg, parm);
754 static void
755 interactive_display_finish (void)
757 clear_link_areas ();
758 g_free (data);
761 void
762 interactive_display (const char *filename, const char *node)
764 WButtonBar *help_bar;
765 Widget *md;
766 char *hlpfile = NULL;
768 if (filename)
769 data = load_file (filename);
770 else
771 data = load_mc_home_file ("mc.hlp", &hlpfile);
773 if (data == NULL) {
774 message (D_ERROR, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
775 unix_error_string (errno));
778 if (!filename)
779 g_free (hlpfile);
781 if (!data)
782 return;
784 if (!node || !*node)
785 node = "[main]";
787 if (!(main_node = search_string (data, node))) {
788 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
789 node);
791 /* Fallback to [main], return if it also cannot be found */
792 main_node = search_string (data, "[main]");
793 if (!main_node) {
794 interactive_display_finish ();
795 return;
799 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
801 whelp =
802 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
803 dialog_colors, help_callback, "[Help]", _("Help"),
804 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
806 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
807 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
809 for (history_ptr = HISTORY_SIZE; history_ptr;) {
810 history_ptr--;
811 history[history_ptr].page = currentpoint;
812 history[history_ptr].link = selected_item;
815 help_bar = buttonbar_new (1);
816 ((Widget *) help_bar)->y -= whelp->y;
817 ((Widget *) help_bar)->x -= whelp->x;
819 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
821 add_widget (whelp, md);
822 add_widget (whelp, help_bar);
824 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
825 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
826 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
827 buttonbar_clear_label (whelp, 4);
828 buttonbar_clear_label (whelp, 5);
829 buttonbar_clear_label (whelp, 6);
830 buttonbar_clear_label (whelp, 7);
831 buttonbar_clear_label (whelp, 8);
832 buttonbar_clear_label (whelp, 9);
833 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
835 run_dlg (whelp);
836 interactive_display_finish ();
837 destroy_dlg (whelp);