Completely removed MHL stuff
[midnight-commander.git] / src / help.c
blob49cf8a28cb8efa0ad74adb23983e2d45e7aaac34
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 "global.h"
52 #include "tty.h"
53 #include "color.h"
54 #include "win.h"
55 #include "mouse.h"
56 #include "key.h" /* For mi_getch() */
57 #include "help.h"
58 #include "dialog.h" /* For Dlg_head */
59 #include "widget.h" /* For Widget */
60 #include "wtools.h" /* For common_dialog_repaint() */
62 #define MAXLINKNAME 80
63 #define HISTORY_SIZE 20
64 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
66 #define STRING_LINK_START "\01"
67 #define STRING_LINK_POINTER "\02"
68 #define STRING_LINK_END "\03"
69 #define STRING_NODE_END "\04"
71 static char *data; /* Pointer to the loaded data file */
72 static int help_lines; /* Lines in help viewer */
73 static int history_ptr; /* For the history queue */
74 static const char *main_node; /* The main node */
75 static const char *last_shown = NULL; /* Last byte shown in a screen */
76 static int end_of_node = 0; /* Flag: the last character of the node shown? */
77 static const char *currentpoint;
78 static const char *selected_item;
80 /* The widget variables */
81 static Dlg_head *whelp;
83 static struct {
84 const char *page; /* Pointer to the selected page */
85 const char *link; /* Pointer to the selected link */
86 } history [HISTORY_SIZE];
88 /* Link areas for the mouse */
89 typedef struct Link_Area {
90 int x1, y1, x2, y2;
91 const char *link_name;
92 struct Link_Area *next;
93 } Link_Area;
95 static Link_Area *link_area = NULL;
96 static int inside_link_area = 0;
98 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
100 /* returns the position where text was found in the start buffer */
101 /* or 0 if not found */
102 static const char *
103 search_string (const char *start, const char *text)
105 const char *result = NULL;
106 char *local_text = g_strdup (text);
107 char *d = local_text;
108 const char *e = start;
110 /* fmt sometimes replaces a space with a newline in the help file */
111 /* Replace the newlines in the link name with spaces to correct the situation */
112 while (*d){
113 if (*d == '\n')
114 *d = ' ';
115 d++;
117 /* Do search */
118 for (d = local_text; *e; e++){
119 if (*d == *e)
120 d++;
121 else
122 d = local_text;
123 if (!*d) {
124 result = e + 1;
125 goto cleanup;
128 cleanup:
129 g_free (local_text);
130 return result;
133 /* Searches text in the buffer pointed by start. Search ends */
134 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
135 static const char *search_string_node (const char *start, const char *text)
137 const char *d = text;
138 const char *e = start;
140 if (!start)
141 return 0;
143 for (; *e && *e != CHAR_NODE_END; e++){
144 if (*d == *e)
145 d++;
146 else
147 d = text;
148 if (!*d)
149 return e+1;
151 return 0;
154 /* Searches the_char in the buffer pointer by start and searches */
155 /* it can search forward (direction = 1) or backward (direction = -1) */
156 static const char *search_char_node (const char *start, char the_char, int direction)
158 const char *e;
160 e = start;
162 for (; *e && (*e != CHAR_NODE_END); e += direction){
163 if (*e == the_char)
164 return e;
166 return 0;
169 /* Returns the new current pointer when moved lines lines */
170 static const char *move_forward2 (const char *c, int lines)
172 const char *p;
173 int line;
175 currentpoint = c;
176 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
177 if (line == lines)
178 return currentpoint = p;
179 if (*p == '\n')
180 line++;
182 return currentpoint = c;
185 static const char *move_backward2 (const char *c, int lines)
187 const char *p;
188 int line;
190 currentpoint = c;
191 for (line = 0, p = currentpoint; *p && p >= data; p--){
192 if (*p == CHAR_NODE_END)
194 /* We reached the beginning of the node */
195 /* Skip the node headers */
196 while (*p != ']') p++;
197 return currentpoint = p + 2; /* Skip the newline following the start of the node */
199 if (*(p - 1) == '\n')
200 line++;
201 if (line == lines)
202 return currentpoint = p;
204 return currentpoint = c;
207 static void move_forward (int i)
209 if (end_of_node)
210 return;
211 currentpoint = move_forward2 (currentpoint, i);
214 static void move_backward (int i)
216 currentpoint = move_backward2 (currentpoint, ++i);
219 static void move_to_top (void)
221 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
222 currentpoint--;
223 while (*currentpoint != ']')
224 currentpoint++;
225 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
226 selected_item = NULL;
229 static void move_to_bottom (void)
231 while (*currentpoint && *currentpoint != CHAR_NODE_END)
232 currentpoint++;
233 currentpoint--;
234 move_backward (help_lines - 1);
237 static const char *help_follow_link (const char *start, const char *selected_item)
239 char link_name [MAXLINKNAME];
240 const char *p;
241 int i = 0;
243 if (!selected_item)
244 return start;
246 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
248 if (*p == CHAR_LINK_POINTER){
249 link_name [0] = '[';
250 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
251 link_name [i++] = *++p;
252 link_name [i-1] = ']';
253 link_name [i] = 0;
254 p = search_string (data, link_name);
255 if (p) {
256 p += 1; /* Skip the newline following the start of the node */
257 return p;
261 /* Create a replacement page with the error message */
262 return _(" Help file format error\n");
265 static const char *select_next_link (const char *current_link)
267 const char *p;
269 if (!current_link)
270 return 0;
272 p = search_string_node (current_link, STRING_LINK_END);
273 if (!p)
274 return NULL;
275 p = search_string_node (p, STRING_LINK_START);
276 if (!p)
277 return NULL;
278 return p - 1;
281 static const char *select_prev_link (const char *current_link)
283 if (!current_link)
284 return 0;
286 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
289 static void start_link_area (int x, int y, const char *link_name)
291 Link_Area *new;
293 if (inside_link_area)
294 message (D_NORMAL, _("Warning"), _(" Internal bug: Double start of link area "));
296 /* Allocate memory for a new link area */
297 new = g_new (Link_Area, 1);
298 new->next = link_area;
299 link_area = new;
301 /* Save the beginning coordinates of the link area */
302 link_area->x1 = x;
303 link_area->y1 = y;
305 /* Save the name of the destination anchor */
306 link_area->link_name = link_name;
308 inside_link_area = 1;
311 static void end_link_area (int x, int y)
313 if (inside_link_area){
314 /* Save the end coordinates of the link area */
315 link_area->x2 = x;
316 link_area->y2 = y;
318 inside_link_area = 0;
322 static void clear_link_areas (void)
324 Link_Area *current;
326 while (link_area){
327 current = link_area;
328 link_area = current -> next;
329 g_free (current);
331 inside_link_area = 0;
334 static void help_show (Dlg_head *h, const char *paint_start)
336 const char *p;
337 int col, line, c;
338 int painting = 1;
339 int acs; /* Flag: Alternate character set active? */
340 int repeat_paint;
341 int active_col, active_line;/* Active link position */
343 attrset (HELP_NORMAL_COLOR);
344 do {
346 line = col = acs = active_col = active_line = repeat_paint = 0;
348 clear_link_areas ();
349 if (selected_item < paint_start)
350 selected_item = NULL;
352 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
353 c = (unsigned char)*p;
354 switch (c){
355 case CHAR_LINK_START:
356 if (selected_item == NULL)
357 selected_item = p;
358 if (p == selected_item){
359 attrset (HELP_SLINK_COLOR);
361 /* Store the coordinates of the link */
362 active_col = col + 2;
363 active_line = line + 2;
365 else
366 attrset (HELP_LINK_COLOR);
367 start_link_area (col, line, p);
368 break;
369 case CHAR_LINK_POINTER:
370 painting = 0;
371 end_link_area (col - 1, line);
372 break;
373 case CHAR_LINK_END:
374 painting = 1;
375 attrset (HELP_NORMAL_COLOR);
376 break;
377 case CHAR_ALTERNATE:
378 acs = 1;
379 break;
380 case CHAR_NORMAL:
381 acs = 0;
382 break;
383 case CHAR_VERSION:
384 dlg_move (h, line+2, col+2);
385 addstr (VERSION);
386 col += strlen (VERSION);
387 break;
388 case CHAR_FONT_BOLD:
389 attrset (HELP_BOLD_COLOR);
390 break;
391 case CHAR_FONT_ITALIC:
392 attrset (HELP_ITALIC_COLOR);
393 break;
394 case CHAR_FONT_NORMAL:
395 attrset (HELP_NORMAL_COLOR);
396 break;
397 case '\n':
398 line++;
399 col = 0;
400 break;
401 case '\t':
402 col = (col/8 + 1) * 8;
403 break;
404 default:
405 if (!painting)
406 continue;
407 if (col > HELP_WINDOW_WIDTH-1)
408 continue;
410 dlg_move (h, line+2, col+2);
411 if (acs){
412 if (c == ' ' || c == '.')
413 addch (c);
414 else
415 #ifndef HAVE_SLANG
416 addch (acs_map [c]);
417 #else
418 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
419 #endif
420 } else
421 addch (c);
422 col++;
423 break;
426 last_shown = p;
427 end_of_node = line < help_lines;
428 attrset (HELP_NORMAL_COLOR);
429 if (selected_item >= last_shown){
430 if (link_area != NULL){
431 selected_item = link_area->link_name;
432 repeat_paint = 1;
434 else
435 selected_item = NULL;
437 } while (repeat_paint);
439 /* Position the cursor over a nice link */
440 if (active_col)
441 dlg_move (h, active_line, active_col);
444 static int
445 help_event (Gpm_Event *event, void *vp)
447 Widget *w = vp;
448 Link_Area *current_area;
450 if (! (event->type & GPM_UP))
451 return 0;
453 /* The event is relative to the dialog window, adjust it: */
454 event->x -= 2;
455 event->y -= 2;
457 if (event->buttons & GPM_B_RIGHT){
458 currentpoint = history [history_ptr].page;
459 selected_item = history [history_ptr].link;
460 history_ptr--;
461 if (history_ptr < 0)
462 history_ptr = HISTORY_SIZE-1;
464 help_callback (w->parent, DLG_DRAW, 0);
465 return 0;
468 /* Test whether the mouse click is inside one of the link areas */
469 current_area = link_area;
470 while (current_area)
472 /* Test one line link area */
473 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
474 event->y == current_area->y2 && event->x <= current_area->x2)
475 break;
476 /* Test two line link area */
477 if (current_area->y1 + 1 == current_area->y2){
478 /* The first line */
479 if (event->y == current_area->y1 && event->x >= current_area->x1)
480 break;
481 /* The second line */
482 if (event->y == current_area->y2 && event->x <= current_area->x2)
483 break;
485 /* Mouse will not work with link areas of more than two lines */
487 current_area = current_area -> next;
490 /* Test whether a link area was found */
491 if (current_area){
492 /* The click was inside a link area -> follow the link */
493 history_ptr = (history_ptr+1) % HISTORY_SIZE;
494 history [history_ptr].page = currentpoint;
495 history [history_ptr].link = current_area->link_name;
496 currentpoint = help_follow_link (currentpoint, current_area->link_name);
497 selected_item = NULL;
498 } else{
499 if (event->y < 0)
500 move_backward (help_lines - 1);
501 else if (event->y >= help_lines)
502 move_forward (help_lines - 1);
503 else if (event->y < help_lines/2)
504 move_backward (1);
505 else
506 move_forward (1);
509 /* Show the new node */
510 help_callback (w->parent, DLG_DRAW, 0);
512 return 0;
515 /* show help */
516 static void
517 help_help_cmd (void *vp)
519 Dlg_head *h = vp;
520 const char *p;
522 history_ptr = (history_ptr+1) % HISTORY_SIZE;
523 history [history_ptr].page = currentpoint;
524 history [history_ptr].link = selected_item;
526 p = search_string(data, "[How to use help]");
527 if (p == NULL)
528 return;
530 currentpoint = p + 1; /* Skip the newline following the start of the node */
531 selected_item = NULL;
532 help_callback (h, DLG_DRAW, 0);
535 static void
536 help_index_cmd (void *vp)
538 Dlg_head *h = vp;
539 const char *new_item;
541 if (!(new_item = search_string (data, "[Contents]"))) {
542 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
543 "[Contents]");
544 return;
547 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
548 history[history_ptr].page = currentpoint;
549 history[history_ptr].link = selected_item;
551 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
552 selected_item = NULL;
553 help_callback (h, DLG_DRAW, 0);
556 static void help_quit_cmd (void *vp)
558 dlg_stop ((Dlg_head *) vp);
561 static void prev_node_cmd (void *vp)
563 Dlg_head *h = vp;
564 currentpoint = history [history_ptr].page;
565 selected_item = history [history_ptr].link;
566 history_ptr--;
567 if (history_ptr < 0)
568 history_ptr = HISTORY_SIZE-1;
570 help_callback (h, DLG_DRAW, 0);
573 static cb_ret_t
574 md_callback (Widget *w, widget_msg_t msg, int parm)
576 (void) w;
577 return default_proc (msg, parm);
580 static Widget *
581 mousedispatch_new (int y, int x, int yl, int xl)
583 Widget *w = g_new (Widget, 1);
585 init_widget (w, y, x, yl, xl, md_callback, help_event);
587 return w;
590 static void help_cmk_move_backward(void *vp, int lines) {
591 (void) &vp;
592 move_backward(lines);
594 static void help_cmk_move_forward(void *vp, int lines) {
595 (void) &vp;
596 move_forward(lines);
598 static void help_cmk_moveto_top(void *vp, int lines) {
599 (void) &vp;
600 (void) &lines;
601 move_to_top();
603 static void help_cmk_moveto_bottom(void *vp, int lines) {
604 (void) &vp;
605 (void) &lines;
606 move_to_bottom();
609 static cb_ret_t
610 help_handle_key (struct Dlg_head *h, int c)
612 const char *new_item;
614 if (c != KEY_UP && c != KEY_DOWN &&
615 check_movement_keys (c, help_lines, NULL,
616 help_cmk_move_backward,
617 help_cmk_move_forward,
618 help_cmk_moveto_top,
619 help_cmk_moveto_bottom)) {
620 /* Nothing */;
621 } else switch (c){
622 case 'l':
623 case KEY_LEFT:
624 prev_node_cmd (h);
625 break;
627 case '\n':
628 case KEY_RIGHT:
629 /* follow link */
630 if (!selected_item){
631 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
632 /* Is there any reason why the right key would take us
633 * backward if there are no links selected?, I agree
634 * with Torben than doing nothing in this case is better
636 /* If there are no links, go backward in history */
637 history_ptr--;
638 if (history_ptr < 0)
639 history_ptr = HISTORY_SIZE-1;
641 currentpoint = history [history_ptr].page;
642 selected_item = history [history_ptr].link;
643 #endif
644 } else {
645 history_ptr = (history_ptr+1) % HISTORY_SIZE;
646 history [history_ptr].page = currentpoint;
647 history [history_ptr].link = selected_item;
648 currentpoint = help_follow_link (currentpoint, selected_item);
650 selected_item = NULL;
651 break;
653 case KEY_DOWN:
654 case '\t':
655 new_item = select_next_link (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 (selected_item);
674 selected_item = new_item;
675 if (selected_item == NULL || selected_item < currentpoint) {
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 MSG_NOT_HANDLED;
730 help_callback (h, DLG_DRAW, 0);
731 return MSG_HANDLED;
734 static cb_ret_t
735 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
737 switch (msg) {
738 case DLG_DRAW:
739 common_dialog_repaint (h);
740 help_show (h, currentpoint);
741 return MSG_HANDLED;
743 case DLG_KEY:
744 return help_handle_key (h, parm);
746 default:
747 return default_dlg_callback (h, msg, parm);
751 static void
752 interactive_display_finish (void)
754 clear_link_areas ();
755 g_free (data);
758 void
759 interactive_display (const char *filename, const char *node)
761 WButtonBar *help_bar;
762 Widget *md;
763 char *hlpfile = NULL;
765 if (filename)
766 data = load_file (filename);
767 else
768 data = load_mc_home_file ("mc.hlp", &hlpfile);
770 if (data == NULL) {
771 message (D_ERROR, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
772 unix_error_string (errno));
775 if (!filename)
776 g_free (hlpfile);
778 if (!data)
779 return;
781 if (!node || !*node)
782 node = "[main]";
784 if (!(main_node = search_string (data, node))) {
785 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
786 node);
788 /* Fallback to [main], return if it also cannot be found */
789 main_node = search_string (data, "[main]");
790 if (!main_node) {
791 interactive_display_finish ();
792 return;
796 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
798 whelp =
799 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
800 dialog_colors, help_callback, "[Help]", _("Help"),
801 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
803 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
804 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
806 for (history_ptr = HISTORY_SIZE; history_ptr;) {
807 history_ptr--;
808 history[history_ptr].page = currentpoint;
809 history[history_ptr].link = selected_item;
812 help_bar = buttonbar_new (1);
813 ((Widget *) help_bar)->y -= whelp->y;
814 ((Widget *) help_bar)->x -= whelp->x;
816 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
818 add_widget (whelp, md);
819 add_widget (whelp, help_bar);
821 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
822 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
823 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
824 buttonbar_clear_label (whelp, 4);
825 buttonbar_clear_label (whelp, 5);
826 buttonbar_clear_label (whelp, 6);
827 buttonbar_clear_label (whelp, 7);
828 buttonbar_clear_label (whelp, 8);
829 buttonbar_clear_label (whelp, 9);
830 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
832 run_dlg (whelp);
833 interactive_display_finish ();
834 destroy_dlg (whelp);