Rework strutils for usage GString instread of self-made buffers.
[midnight-commander.git] / src / help.c
bloba5bc256d823699a516daf49265f25a59268e8e87
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() */
61 #include "strutil.h"
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;
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 str_next_char (&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;
178 str_cnext_char (&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;
195 str_cprev_char (&p)) {
197 if (*p == CHAR_NODE_END)
199 /* We reached the beginning of the node */
200 /* Skip the node headers */
201 while (*p != ']') str_cnext_char (&p);
202 return currentpoint = p + 2; /* Skip the newline following the start of the node */
204 if (*(p - 1) == '\n')
205 line++;
206 if (line == lines)
207 return currentpoint = p;
209 return currentpoint = c;
212 static void move_forward (int i)
214 if (end_of_node)
215 return;
216 currentpoint = move_forward2 (currentpoint, i);
219 static void move_backward (int i)
221 currentpoint = move_backward2 (currentpoint, ++i);
224 static void move_to_top (void)
226 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
227 currentpoint--;
228 while (*currentpoint != ']')
229 currentpoint++;
230 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
231 selected_item = NULL;
234 static void move_to_bottom (void)
236 while (*currentpoint && *currentpoint != CHAR_NODE_END)
237 currentpoint++;
238 currentpoint--;
239 move_backward (help_lines - 1);
242 static const char *help_follow_link (const char *start, const char *selected_item)
244 char link_name [MAXLINKNAME];
245 const char *p;
246 int i = 0;
248 if (!selected_item)
249 return start;
251 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
253 if (*p == CHAR_LINK_POINTER){
254 link_name [0] = '[';
255 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
256 link_name [i++] = *++p;
257 link_name [i-1] = ']';
258 link_name [i] = 0;
259 p = search_string (data, link_name);
260 if (p) {
261 p += 1; /* Skip the newline following the start of the node */
262 return p;
266 /* Create a replacement page with the error message */
267 return _(" Help file format error\n");
270 static const char *select_next_link (const char *current_link)
272 const char *p;
274 if (!current_link)
275 return 0;
277 p = search_string_node (current_link, STRING_LINK_END);
278 if (!p)
279 return NULL;
280 p = search_string_node (p, STRING_LINK_START);
281 if (!p)
282 return NULL;
283 return p - 1;
286 static const char *select_prev_link (const char *current_link)
288 if (!current_link)
289 return 0;
291 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
294 static void start_link_area (int x, int y, const char *link_name)
296 Link_Area *new;
298 if (inside_link_area)
299 message (D_NORMAL, _("Warning"), _(" Internal bug: Double start of link area "));
301 /* Allocate memory for a new link area */
302 new = g_new (Link_Area, 1);
303 new->next = link_area;
304 link_area = new;
306 /* Save the beginning coordinates of the link area */
307 link_area->x1 = x;
308 link_area->y1 = y;
310 /* Save the name of the destination anchor */
311 link_area->link_name = link_name;
313 inside_link_area = 1;
316 static void end_link_area (int x, int y)
318 if (inside_link_area){
319 /* Save the end coordinates of the link area */
320 link_area->x2 = x;
321 link_area->y2 = y;
323 inside_link_area = 0;
327 static void clear_link_areas (void)
329 Link_Area *current;
331 while (link_area){
332 current = link_area;
333 link_area = current -> next;
334 g_free (current);
336 inside_link_area = 0;
339 static void help_show (Dlg_head *h, const char *paint_start)
341 const char *p, *n;
342 int col, line, c, w;
343 int painting = 1;
344 int acs; /* Flag: Alternate character set active? */
345 int repeat_paint;
346 int active_col, active_line;/* Active link position */
347 static char buff[MB_LEN_MAX + 1];
349 attrset (HELP_NORMAL_COLOR);
350 do {
352 line = col = acs = active_col = active_line = repeat_paint = 0;
354 clear_link_areas ();
355 if (selected_item < paint_start)
356 selected_item = NULL;
358 p = paint_start;
359 n = paint_start;
360 while (n[0] != '\0' && n[0] != CHAR_NODE_END && line < help_lines) {
361 p = n;
362 n = str_cget_next_char (p);
363 memcpy (buff, p, n - p);
364 buff[n - p] = '\0';
365 c = (unsigned char) buff[0];
367 switch (c){
368 case CHAR_LINK_START:
369 if (selected_item == NULL)
370 selected_item = p;
371 if (p == selected_item){
372 attrset (HELP_SLINK_COLOR);
374 /* Store the coordinates of the link */
375 active_col = col + 2;
376 active_line = line + 2;
378 else
379 attrset (HELP_LINK_COLOR);
380 start_link_area (col, line, p);
381 break;
382 case CHAR_LINK_POINTER:
383 painting = 0;
384 end_link_area (col - 1, line);
385 break;
386 case CHAR_LINK_END:
387 painting = 1;
388 attrset (HELP_NORMAL_COLOR);
389 break;
390 case CHAR_ALTERNATE:
391 acs = 1;
392 break;
393 case CHAR_NORMAL:
394 acs = 0;
395 break;
396 case CHAR_VERSION:
397 dlg_move (h, line+2, col+2);
398 addstr (VERSION);
399 col += str_term_width1 (VERSION);
400 break;
401 case CHAR_FONT_BOLD:
402 attrset (HELP_BOLD_COLOR);
403 break;
404 case CHAR_FONT_ITALIC:
405 attrset (HELP_ITALIC_COLOR);
406 break;
407 case CHAR_FONT_NORMAL:
408 attrset (HELP_NORMAL_COLOR);
409 break;
410 case '\n':
411 line++;
412 col = 0;
413 break;
414 case '\t':
415 col = (col / 8 + 1) * 8;
416 break;
417 default:
418 if (!painting)
419 continue;
420 w = str_term_width1 (buff);
421 if (col + w > HELP_WINDOW_WIDTH)
422 continue;
424 dlg_move (h, line+2, col+2);
425 if (acs){
426 if (c == ' ' || c == '.')
427 addch (c);
428 else
429 #ifndef HAVE_SLANG
430 addch (acs_map [c]);
431 #else
432 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
433 #endif
434 } else {
435 addstr (buff);
437 col+= w;
438 break;
441 last_shown = p;
442 end_of_node = line < help_lines;
443 attrset (HELP_NORMAL_COLOR);
444 if (selected_item >= last_shown){
445 if (link_area != NULL){
446 selected_item = link_area->link_name;
447 repeat_paint = 1;
449 else
450 selected_item = NULL;
452 } while (repeat_paint);
454 /* Position the cursor over a nice link */
455 if (active_col)
456 dlg_move (h, active_line, active_col);
459 static int
460 help_event (Gpm_Event *event, void *vp)
462 Widget *w = vp;
463 Link_Area *current_area;
465 if (! (event->type & GPM_UP))
466 return 0;
468 /* The event is relative to the dialog window, adjust it: */
469 event->x -= 2;
470 event->y -= 2;
472 if (event->buttons & GPM_B_RIGHT){
473 currentpoint = history [history_ptr].page;
474 selected_item = history [history_ptr].link;
475 history_ptr--;
476 if (history_ptr < 0)
477 history_ptr = HISTORY_SIZE-1;
479 help_callback (w->parent, DLG_DRAW, 0);
480 return 0;
483 /* Test whether the mouse click is inside one of the link areas */
484 current_area = link_area;
485 while (current_area)
487 /* Test one line link area */
488 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
489 event->y == current_area->y2 && event->x <= current_area->x2)
490 break;
491 /* Test two line link area */
492 if (current_area->y1 + 1 == current_area->y2){
493 /* The first line */
494 if (event->y == current_area->y1 && event->x >= current_area->x1)
495 break;
496 /* The second line */
497 if (event->y == current_area->y2 && event->x <= current_area->x2)
498 break;
500 /* Mouse will not work with link areas of more than two lines */
502 current_area = current_area -> next;
505 /* Test whether a link area was found */
506 if (current_area){
507 /* The click was inside a link area -> follow the link */
508 history_ptr = (history_ptr+1) % HISTORY_SIZE;
509 history [history_ptr].page = currentpoint;
510 history [history_ptr].link = current_area->link_name;
511 currentpoint = help_follow_link (currentpoint, current_area->link_name);
512 selected_item = NULL;
513 } else{
514 if (event->y < 0)
515 move_backward (help_lines - 1);
516 else if (event->y >= help_lines)
517 move_forward (help_lines - 1);
518 else if (event->y < help_lines/2)
519 move_backward (1);
520 else
521 move_forward (1);
524 /* Show the new node */
525 help_callback (w->parent, DLG_DRAW, 0);
527 return 0;
530 /* show help */
531 static void
532 help_help_cmd (void *vp)
534 Dlg_head *h = vp;
535 const char *p;
537 history_ptr = (history_ptr+1) % HISTORY_SIZE;
538 history [history_ptr].page = currentpoint;
539 history [history_ptr].link = selected_item;
541 p = search_string(data, "[How to use help]");
542 if (p == NULL)
543 return;
545 currentpoint = p + 1; /* Skip the newline following the start of the node */
546 selected_item = NULL;
547 help_callback (h, DLG_DRAW, 0);
550 static void
551 help_index_cmd (void *vp)
553 Dlg_head *h = vp;
554 const char *new_item;
556 if (!(new_item = search_string (data, "[Contents]"))) {
557 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
558 "[Contents]");
559 return;
562 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
563 history[history_ptr].page = currentpoint;
564 history[history_ptr].link = selected_item;
566 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
567 selected_item = NULL;
568 help_callback (h, DLG_DRAW, 0);
571 static void help_quit_cmd (void *vp)
573 dlg_stop ((Dlg_head *) vp);
576 static void prev_node_cmd (void *vp)
578 Dlg_head *h = vp;
579 currentpoint = history [history_ptr].page;
580 selected_item = history [history_ptr].link;
581 history_ptr--;
582 if (history_ptr < 0)
583 history_ptr = HISTORY_SIZE-1;
585 help_callback (h, DLG_DRAW, 0);
588 static cb_ret_t
589 md_callback (Widget *w, widget_msg_t msg, int parm)
591 (void) w;
592 return default_proc (msg, parm);
595 static Widget *
596 mousedispatch_new (int y, int x, int yl, int xl)
598 Widget *w = g_new (Widget, 1);
600 init_widget (w, y, x, yl, xl, md_callback, help_event);
602 return w;
605 static void help_cmk_move_backward(void *vp, int lines) {
606 (void) &vp;
607 move_backward(lines);
609 static void help_cmk_move_forward(void *vp, int lines) {
610 (void) &vp;
611 move_forward(lines);
613 static void help_cmk_moveto_top(void *vp, int lines) {
614 (void) &vp;
615 (void) &lines;
616 move_to_top();
618 static void help_cmk_moveto_bottom(void *vp, int lines) {
619 (void) &vp;
620 (void) &lines;
621 move_to_bottom();
624 static cb_ret_t
625 help_handle_key (struct Dlg_head *h, int c)
627 const char *new_item;
629 if (c != KEY_UP && c != KEY_DOWN &&
630 check_movement_keys (c, help_lines, NULL,
631 help_cmk_move_backward,
632 help_cmk_move_forward,
633 help_cmk_moveto_top,
634 help_cmk_moveto_bottom)) {
635 /* Nothing */;
636 } else switch (c){
637 case 'l':
638 case KEY_LEFT:
639 prev_node_cmd (h);
640 break;
642 case '\n':
643 case KEY_RIGHT:
644 /* follow link */
645 if (!selected_item){
646 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
647 /* Is there any reason why the right key would take us
648 * backward if there are no links selected?, I agree
649 * with Torben than doing nothing in this case is better
651 /* If there are no links, go backward in history */
652 history_ptr--;
653 if (history_ptr < 0)
654 history_ptr = HISTORY_SIZE-1;
656 currentpoint = history [history_ptr].page;
657 selected_item = history [history_ptr].link;
658 #endif
659 } else {
660 history_ptr = (history_ptr+1) % HISTORY_SIZE;
661 history [history_ptr].page = currentpoint;
662 history [history_ptr].link = selected_item;
663 currentpoint = help_follow_link (currentpoint, selected_item);
665 selected_item = NULL;
666 break;
668 case KEY_DOWN:
669 case '\t':
670 new_item = select_next_link (selected_item);
671 if (new_item){
672 selected_item = new_item;
673 if (selected_item >= last_shown){
674 if (c == KEY_DOWN)
675 move_forward (1);
676 else
677 selected_item = NULL;
679 } else if (c == KEY_DOWN)
680 move_forward (1);
681 else
682 selected_item = NULL;
683 break;
685 case KEY_UP:
686 case ALT ('\t'):
687 /* select previous link */
688 new_item = select_prev_link (selected_item);
689 selected_item = new_item;
690 if (selected_item == NULL || selected_item < currentpoint) {
691 if (c == KEY_UP)
692 move_backward (1);
693 else{
694 if (link_area != NULL)
695 selected_item = link_area->link_name;
696 else
697 selected_item = NULL;
700 break;
702 case 'n':
703 /* Next node */
704 new_item = currentpoint;
705 while (*new_item && *new_item != CHAR_NODE_END)
706 new_item++;
707 if (*++new_item == '['){
708 while (*++new_item) {
709 if (*new_item == ']' && *++new_item && *++new_item) {
710 currentpoint = new_item;
711 selected_item = NULL;
712 break;
716 break;
718 case 'p':
719 /* Previous node */
720 new_item = currentpoint;
721 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
722 new_item--;
723 new_item--;
724 while (new_item > data && *new_item != CHAR_NODE_END)
725 new_item--;
726 while (*new_item != ']')
727 new_item++;
728 currentpoint = new_item + 2;
729 selected_item = NULL;
730 break;
732 case 'c':
733 help_index_cmd (h);
734 break;
736 case ESC_CHAR:
737 case XCTRL('g'):
738 dlg_stop (h);
739 break;
741 default:
742 return MSG_NOT_HANDLED;
745 help_callback (h, DLG_DRAW, 0);
746 return MSG_HANDLED;
749 static cb_ret_t
750 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
752 switch (msg) {
753 case DLG_DRAW:
754 common_dialog_repaint (h);
755 help_show (h, currentpoint);
756 return MSG_HANDLED;
758 case DLG_KEY:
759 return help_handle_key (h, parm);
761 default:
762 return default_dlg_callback (h, msg, parm);
766 static void
767 interactive_display_finish (void)
769 clear_link_areas ();
772 /* translate help file into terminal encoding */
773 static void
774 translate_file (char *filedata)
776 GIConv conv;
777 GString *translated_data;
779 translated_data = g_string_new ("");
781 conv = str_crt_conv_from ("UTF-8");
783 if (conv != INVALID_CONV) {
784 if (str_convert (conv, filedata, translated_data) != ESTR_FAILURE) {
785 data = translated_data->str;
786 } else {
787 data = NULL;
789 str_close_conv (conv);
791 g_string_free (translated_data, TRUE);
794 void
795 interactive_display (const char *filename, const char *node)
797 WButtonBar *help_bar;
798 Widget *md;
799 char *hlpfile = NULL;
800 char *filedata;
802 if (filename)
803 filedata = load_file (filename);
804 else
805 filedata = load_mc_home_file ("mc.hlp", &hlpfile);
807 if (data == NULL) {
808 message (D_ERROR, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
809 unix_error_string (errno));
812 if (!filename)
813 g_free (hlpfile);
815 if (filedata == NULL)
816 return;
818 translate_file (filedata);
820 g_free (filedata);
822 if (!data)
823 return;
825 if (!node || !*node)
826 node = "[main]";
828 if (!(main_node = search_string (data, node))) {
829 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
830 node);
832 /* Fallback to [main], return if it also cannot be found */
833 main_node = search_string (data, "[main]");
834 if (!main_node) {
835 interactive_display_finish ();
836 return;
840 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
842 whelp =
843 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
844 dialog_colors, help_callback, "[Help]", _("Help"),
845 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
847 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
848 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
850 for (history_ptr = HISTORY_SIZE; history_ptr;) {
851 history_ptr--;
852 history[history_ptr].page = currentpoint;
853 history[history_ptr].link = selected_item;
856 help_bar = buttonbar_new (1);
857 ((Widget *) help_bar)->y -= whelp->y;
858 ((Widget *) help_bar)->x -= whelp->x;
860 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
862 add_widget (whelp, md);
863 add_widget (whelp, help_bar);
865 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
866 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
867 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
868 buttonbar_clear_label (whelp, 4);
869 buttonbar_clear_label (whelp, 5);
870 buttonbar_clear_label (whelp, 6);
871 buttonbar_clear_label (whelp, 7);
872 buttonbar_clear_label (whelp, 8);
873 buttonbar_clear_label (whelp, 9);
874 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
876 run_dlg (whelp);
877 interactive_display_finish ();
878 destroy_dlg (whelp);