Ticket 419: (search broken in editor/viewer on glib <2.14)
[midnight-commander.git] / src / help.c
blob99b7f4af380afad81f9df64a7e249f6048242cbf
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 /** \file help.c
22 * \brief Source: hypertext file browser
24 * Implements the hypertext file viewer.
25 * The hypertext file is a file that may have one or more nodes. Each
26 * node ends with a ^D character and starts with a bracket, then the
27 * name of the node and then a closing bracket. Right after the closing
28 * bracket a newline is placed. This newline is not to be displayed by
29 * the help viewer and must be skipped - its sole purpose is to faciliate
30 * the work of the people managing the help file template (xnc.hlp) .
32 * Links in the hypertext file are specified like this: the text that
33 * will be highlighted should have a leading ^A, then it comes the
34 * text, then a ^B indicating that highlighting is done, then the name
35 * of the node you want to link to and then a ^C.
37 * The file must contain a ^D at the beginning and at the end of the
38 * file or the program will not be able to detect the end of file.
40 * Lazyness/widgeting attack: This file does use the dialog manager
41 * and uses mainly the dialog to achieve the help work. there is only
42 * one specialized widget and it's only used to forward the mouse messages
43 * to the appropiate routine.
47 #include <config.h>
49 #include <errno.h>
50 #include <stdio.h>
51 #include <sys/types.h>
52 #include <sys/stat.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() */
64 #include "strutil.h"
66 #define MAXLINKNAME 80
67 #define HISTORY_SIZE 20
68 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
70 #define STRING_LINK_START "\01"
71 #define STRING_LINK_POINTER "\02"
72 #define STRING_LINK_END "\03"
73 #define STRING_NODE_END "\04"
75 static char *data = NULL; /* Pointer to the loaded data file */
76 static int help_lines; /* Lines in help viewer */
77 static int history_ptr; /* For the history queue */
78 static const char *main_node; /* The main node */
79 static const char *last_shown = NULL; /* Last byte shown in a screen */
80 static int end_of_node = 0; /* Flag: the last character of the node shown? */
81 static const char *currentpoint;
82 static const char *selected_item;
84 /* The widget variables */
85 static Dlg_head *whelp;
87 static struct {
88 const char *page; /* Pointer to the selected page */
89 const char *link; /* Pointer to the selected link */
90 } history [HISTORY_SIZE];
92 /* Link areas for the mouse */
93 typedef struct Link_Area {
94 int x1, y1, x2, y2;
95 const char *link_name;
96 struct Link_Area *next;
97 } Link_Area;
99 static Link_Area *link_area = NULL;
100 static int inside_link_area = 0;
102 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
104 /* returns the position where text was found in the start buffer */
105 /* or 0 if not found */
106 static const char *
107 search_string (const char *start, const char *text)
109 const char *result = NULL;
110 char *local_text = g_strdup (text);
111 char *d = local_text;
112 const char *e = start;
114 /* fmt sometimes replaces a space with a newline in the help file */
115 /* Replace the newlines in the link name with spaces to correct the situation */
116 while (*d){
117 if (*d == '\n')
118 *d = ' ';
119 str_next_char (&d);
121 /* Do search */
122 for (d = local_text; *e; e++){
123 if (*d == *e)
124 d++;
125 else
126 d = local_text;
127 if (!*d) {
128 result = e + 1;
129 goto cleanup;
132 cleanup:
133 g_free (local_text);
134 return result;
137 /* Searches text in the buffer pointed by start. Search ends */
138 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
139 static const char *search_string_node (const char *start, const char *text)
141 const char *d = text;
142 const char *e = start;
144 if (!start)
145 return 0;
147 for (; *e && *e != CHAR_NODE_END; e++){
148 if (*d == *e)
149 d++;
150 else
151 d = text;
152 if (!*d)
153 return e+1;
155 return 0;
158 /* Searches the_char in the buffer pointer by start and searches */
159 /* it can search forward (direction = 1) or backward (direction = -1) */
160 static const char *search_char_node (const char *start, char the_char, int direction)
162 const char *e;
164 e = start;
166 for (; *e && (*e != CHAR_NODE_END); e += direction){
167 if (*e == the_char)
168 return e;
170 return 0;
173 /* Returns the new current pointer when moved lines lines */
174 static const char *move_forward2 (const char *c, int lines)
176 const char *p;
177 int line;
179 currentpoint = c;
180 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END;
181 str_cnext_char (&p)){
183 if (line == lines)
184 return currentpoint = p;
185 if (*p == '\n')
186 line++;
188 return currentpoint = c;
191 static const char *move_backward2 (const char *c, int lines)
193 const char *p;
194 int line;
196 currentpoint = c;
197 for (line = 0, p = currentpoint; *p && p >= data;
198 str_cprev_char (&p)) {
200 if (*p == CHAR_NODE_END)
202 /* We reached the beginning of the node */
203 /* Skip the node headers */
204 while (*p != ']') str_cnext_char (&p);
205 return currentpoint = p + 2; /* Skip the newline following the start of the node */
207 if (*(p - 1) == '\n')
208 line++;
209 if (line == lines)
210 return currentpoint = p;
212 return currentpoint = c;
215 static void move_forward (int i)
217 if (end_of_node)
218 return;
219 currentpoint = move_forward2 (currentpoint, i);
222 static void move_backward (int i)
224 currentpoint = move_backward2 (currentpoint, ++i);
227 static void move_to_top (void)
229 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
230 currentpoint--;
231 while (*currentpoint != ']')
232 currentpoint++;
233 currentpoint = currentpoint + 2; /* Skip the newline following the start of the node */
234 selected_item = NULL;
237 static void move_to_bottom (void)
239 while (*currentpoint && *currentpoint != CHAR_NODE_END)
240 currentpoint++;
241 currentpoint--;
242 move_backward (help_lines - 1);
245 static const char *help_follow_link (const char *start, const char *selected_item)
247 char link_name [MAXLINKNAME];
248 const char *p;
249 int i = 0;
251 if (!selected_item)
252 return start;
254 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
256 if (*p == CHAR_LINK_POINTER){
257 link_name [0] = '[';
258 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
259 link_name [i++] = *++p;
260 link_name [i-1] = ']';
261 link_name [i] = 0;
262 p = search_string (data, link_name);
263 if (p) {
264 p += 1; /* Skip the newline following the start of the node */
265 return p;
269 /* Create a replacement page with the error message */
270 return _(" Help file format error\n");
273 static const char *select_next_link (const char *current_link)
275 const char *p;
277 if (!current_link)
278 return 0;
280 p = search_string_node (current_link, STRING_LINK_END);
281 if (!p)
282 return NULL;
283 p = search_string_node (p, STRING_LINK_START);
284 if (!p)
285 return NULL;
286 return p - 1;
289 static const char *select_prev_link (const char *current_link)
291 if (!current_link)
292 return 0;
294 return search_char_node (current_link - 1, CHAR_LINK_START, -1);
297 static void start_link_area (int x, int y, const char *link_name)
299 Link_Area *new;
301 if (inside_link_area)
302 message (D_NORMAL, _("Warning"), _(" Internal bug: Double start of link area "));
304 /* Allocate memory for a new link area */
305 new = g_new (Link_Area, 1);
306 new->next = link_area;
307 link_area = new;
309 /* Save the beginning coordinates of the link area */
310 link_area->x1 = x;
311 link_area->y1 = y;
313 /* Save the name of the destination anchor */
314 link_area->link_name = link_name;
316 inside_link_area = 1;
319 static void end_link_area (int x, int y)
321 if (inside_link_area){
322 /* Save the end coordinates of the link area */
323 link_area->x2 = x;
324 link_area->y2 = y;
326 inside_link_area = 0;
330 static void clear_link_areas (void)
332 Link_Area *current;
334 while (link_area){
335 current = link_area;
336 link_area = current -> next;
337 g_free (current);
339 inside_link_area = 0;
342 static void help_show (Dlg_head *h, const char *paint_start)
344 const char *p, *n;
345 int col, line, c, w;
346 int painting = 1;
347 int acs; /* Flag: Alternate character set active? */
348 int repeat_paint;
349 int active_col, active_line;/* Active link position */
350 static char buff[MB_LEN_MAX + 1];
352 attrset (HELP_NORMAL_COLOR);
353 do {
355 line = col = acs = active_col = active_line = repeat_paint = 0;
357 clear_link_areas ();
358 if (selected_item < paint_start)
359 selected_item = NULL;
361 p = paint_start;
362 n = paint_start;
363 while (n[0] != '\0' && n[0] != CHAR_NODE_END && line < help_lines) {
364 p = n;
365 n = str_cget_next_char (p);
366 memcpy (buff, p, n - p);
367 buff[n - p] = '\0';
368 c = (unsigned char) buff[0];
370 switch (c){
371 case CHAR_LINK_START:
372 if (selected_item == NULL)
373 selected_item = p;
374 if (p == selected_item){
375 attrset (HELP_SLINK_COLOR);
377 /* Store the coordinates of the link */
378 active_col = col + 2;
379 active_line = line + 2;
381 else
382 attrset (HELP_LINK_COLOR);
383 start_link_area (col, line, p);
384 break;
385 case CHAR_LINK_POINTER:
386 painting = 0;
387 end_link_area (col - 1, line);
388 break;
389 case CHAR_LINK_END:
390 painting = 1;
391 attrset (HELP_NORMAL_COLOR);
392 break;
393 case CHAR_ALTERNATE:
394 acs = 1;
395 break;
396 case CHAR_NORMAL:
397 acs = 0;
398 break;
399 case CHAR_VERSION:
400 dlg_move (h, line+2, col+2);
401 addstr (VERSION);
402 col += str_term_width1 (VERSION);
403 break;
404 case CHAR_FONT_BOLD:
405 attrset (HELP_BOLD_COLOR);
406 break;
407 case CHAR_FONT_ITALIC:
408 attrset (HELP_ITALIC_COLOR);
409 break;
410 case CHAR_FONT_NORMAL:
411 attrset (HELP_NORMAL_COLOR);
412 break;
413 case '\n':
414 line++;
415 col = 0;
416 break;
417 case '\t':
418 col = (col / 8 + 1) * 8;
419 break;
420 default:
421 if (!painting)
422 continue;
423 w = str_term_width1 (buff);
424 if (col + w > HELP_WINDOW_WIDTH)
425 continue;
427 dlg_move (h, line+2, col+2);
428 if (acs){
429 if (c == ' ' || c == '.')
430 addch (c);
431 else
432 #ifndef HAVE_SLANG
433 addch (acs_map [c]);
434 #else
435 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
436 #endif
437 } else {
438 addstr (buff);
440 col+= w;
441 break;
444 last_shown = p;
445 end_of_node = line < help_lines;
446 attrset (HELP_NORMAL_COLOR);
447 if (selected_item >= last_shown){
448 if (link_area != NULL){
449 selected_item = link_area->link_name;
450 repeat_paint = 1;
452 else
453 selected_item = NULL;
455 } while (repeat_paint);
457 /* Position the cursor over a nice link */
458 if (active_col)
459 dlg_move (h, active_line, active_col);
462 static int
463 help_event (Gpm_Event *event, void *vp)
465 Widget *w = vp;
466 Link_Area *current_area;
468 if (! (event->type & GPM_UP))
469 return 0;
471 /* The event is relative to the dialog window, adjust it: */
472 event->x -= 2;
473 event->y -= 2;
475 if (event->buttons & GPM_B_RIGHT){
476 currentpoint = history [history_ptr].page;
477 selected_item = history [history_ptr].link;
478 history_ptr--;
479 if (history_ptr < 0)
480 history_ptr = HISTORY_SIZE-1;
482 help_callback (w->parent, DLG_DRAW, 0);
483 return 0;
486 /* Test whether the mouse click is inside one of the link areas */
487 current_area = link_area;
488 while (current_area)
490 /* Test one line link area */
491 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
492 event->y == current_area->y2 && event->x <= current_area->x2)
493 break;
494 /* Test two line link area */
495 if (current_area->y1 + 1 == current_area->y2){
496 /* The first line */
497 if (event->y == current_area->y1 && event->x >= current_area->x1)
498 break;
499 /* The second line */
500 if (event->y == current_area->y2 && event->x <= current_area->x2)
501 break;
503 /* Mouse will not work with link areas of more than two lines */
505 current_area = current_area -> next;
508 /* Test whether a link area was found */
509 if (current_area){
510 /* The click was inside a link area -> follow the link */
511 history_ptr = (history_ptr+1) % HISTORY_SIZE;
512 history [history_ptr].page = currentpoint;
513 history [history_ptr].link = current_area->link_name;
514 currentpoint = help_follow_link (currentpoint, current_area->link_name);
515 selected_item = NULL;
516 } else{
517 if (event->y < 0)
518 move_backward (help_lines - 1);
519 else if (event->y >= help_lines)
520 move_forward (help_lines - 1);
521 else if (event->y < help_lines/2)
522 move_backward (1);
523 else
524 move_forward (1);
527 /* Show the new node */
528 help_callback (w->parent, DLG_DRAW, 0);
530 return 0;
533 /* show help */
534 static void
535 help_help_cmd (void *vp)
537 Dlg_head *h = vp;
538 const char *p;
540 history_ptr = (history_ptr+1) % HISTORY_SIZE;
541 history [history_ptr].page = currentpoint;
542 history [history_ptr].link = selected_item;
544 p = search_string(data, "[How to use help]");
545 if (p == NULL)
546 return;
548 currentpoint = p + 1; /* Skip the newline following the start of the node */
549 selected_item = NULL;
550 help_callback (h, DLG_DRAW, 0);
553 static void
554 help_index_cmd (void *vp)
556 Dlg_head *h = vp;
557 const char *new_item;
559 if (!(new_item = search_string (data, "[Contents]"))) {
560 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
561 "[Contents]");
562 return;
565 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
566 history[history_ptr].page = currentpoint;
567 history[history_ptr].link = selected_item;
569 currentpoint = new_item + 1; /* Skip the newline following the start of the node */
570 selected_item = NULL;
571 help_callback (h, DLG_DRAW, 0);
574 static void help_quit_cmd (void *vp)
576 dlg_stop ((Dlg_head *) vp);
579 static void prev_node_cmd (void *vp)
581 Dlg_head *h = vp;
582 currentpoint = history [history_ptr].page;
583 selected_item = history [history_ptr].link;
584 history_ptr--;
585 if (history_ptr < 0)
586 history_ptr = HISTORY_SIZE-1;
588 help_callback (h, DLG_DRAW, 0);
591 static cb_ret_t
592 md_callback (Widget *w, widget_msg_t msg, int parm)
594 (void) w;
595 return default_proc (msg, parm);
598 static Widget *
599 mousedispatch_new (int y, int x, int yl, int xl)
601 Widget *w = g_new (Widget, 1);
603 init_widget (w, y, x, yl, xl, md_callback, help_event);
605 return w;
608 static void help_cmk_move_backward(void *vp, int lines) {
609 (void) &vp;
610 move_backward(lines);
612 static void help_cmk_move_forward(void *vp, int lines) {
613 (void) &vp;
614 move_forward(lines);
616 static void help_cmk_moveto_top(void *vp, int lines) {
617 (void) &vp;
618 (void) &lines;
619 move_to_top();
621 static void help_cmk_moveto_bottom(void *vp, int lines) {
622 (void) &vp;
623 (void) &lines;
624 move_to_bottom();
627 static cb_ret_t
628 help_handle_key (struct Dlg_head *h, int c)
630 const char *new_item;
632 if (c != KEY_UP && c != KEY_DOWN &&
633 check_movement_keys (c, help_lines, NULL,
634 help_cmk_move_backward,
635 help_cmk_move_forward,
636 help_cmk_moveto_top,
637 help_cmk_moveto_bottom)) {
638 /* Nothing */;
639 } else switch (c){
640 case 'l':
641 case KEY_LEFT:
642 prev_node_cmd (h);
643 break;
645 case '\n':
646 case KEY_RIGHT:
647 /* follow link */
648 if (!selected_item){
649 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
650 /* Is there any reason why the right key would take us
651 * backward if there are no links selected?, I agree
652 * with Torben than doing nothing in this case is better
654 /* If there are no links, go backward in history */
655 history_ptr--;
656 if (history_ptr < 0)
657 history_ptr = HISTORY_SIZE-1;
659 currentpoint = history [history_ptr].page;
660 selected_item = history [history_ptr].link;
661 #endif
662 } else {
663 history_ptr = (history_ptr+1) % HISTORY_SIZE;
664 history [history_ptr].page = currentpoint;
665 history [history_ptr].link = selected_item;
666 currentpoint = help_follow_link (currentpoint, selected_item);
668 selected_item = NULL;
669 break;
671 case KEY_DOWN:
672 case '\t':
673 new_item = select_next_link (selected_item);
674 if (new_item){
675 selected_item = new_item;
676 if (selected_item >= last_shown){
677 if (c == KEY_DOWN)
678 move_forward (1);
679 else
680 selected_item = NULL;
682 } else if (c == KEY_DOWN)
683 move_forward (1);
684 else
685 selected_item = NULL;
686 break;
688 case KEY_UP:
689 case ALT ('\t'):
690 /* select previous link */
691 new_item = select_prev_link (selected_item);
692 selected_item = new_item;
693 if (selected_item == NULL || selected_item < currentpoint) {
694 if (c == KEY_UP)
695 move_backward (1);
696 else{
697 if (link_area != NULL)
698 selected_item = link_area->link_name;
699 else
700 selected_item = NULL;
703 break;
705 case 'n':
706 /* Next node */
707 new_item = currentpoint;
708 while (*new_item && *new_item != CHAR_NODE_END)
709 new_item++;
710 if (*++new_item == '['){
711 while (*++new_item) {
712 if (*new_item == ']' && *++new_item && *++new_item) {
713 currentpoint = new_item;
714 selected_item = NULL;
715 break;
719 break;
721 case 'p':
722 /* Previous node */
723 new_item = currentpoint;
724 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
725 new_item--;
726 new_item--;
727 while (new_item > data && *new_item != CHAR_NODE_END)
728 new_item--;
729 while (*new_item != ']')
730 new_item++;
731 currentpoint = new_item + 2;
732 selected_item = NULL;
733 break;
735 case 'c':
736 help_index_cmd (h);
737 break;
739 case ESC_CHAR:
740 case XCTRL('g'):
741 dlg_stop (h);
742 break;
744 default:
745 return MSG_NOT_HANDLED;
748 help_callback (h, DLG_DRAW, 0);
749 return MSG_HANDLED;
752 static cb_ret_t
753 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
755 switch (msg) {
756 case DLG_DRAW:
757 common_dialog_repaint (h);
758 help_show (h, currentpoint);
759 return MSG_HANDLED;
761 case DLG_KEY:
762 return help_handle_key (h, parm);
764 default:
765 return default_dlg_callback (h, msg, parm);
769 static void
770 interactive_display_finish (void)
772 clear_link_areas ();
775 /* translate help file into terminal encoding */
776 static void
777 translate_file (char *filedata)
779 GIConv conv;
780 GString *translated_data;
782 translated_data = g_string_new ("");
784 conv = str_crt_conv_from ("UTF-8");
786 if (conv != INVALID_CONV) {
787 g_free (data);
789 if (str_convert (conv, filedata, translated_data) != ESTR_FAILURE) {
790 data = translated_data->str;
791 g_string_free (translated_data, FALSE);
792 } else {
793 data = NULL;
794 g_string_free (translated_data, TRUE);
796 str_close_conv (conv);
797 } else
798 g_string_free (translated_data, TRUE);
801 void
802 interactive_display (const char *filename, const char *node)
804 WButtonBar *help_bar;
805 Widget *md;
806 char *hlpfile = NULL;
807 char *filedata;
809 if (filename)
810 filedata = load_file (filename);
811 else
812 filedata = load_mc_home_file ("mc.hlp", &hlpfile);
814 if (filedata == NULL) {
815 message (D_ERROR, MSG_ERROR, _(" Cannot open file %s \n %s "), filename ? filename : hlpfile,
816 unix_error_string (errno));
819 if (!filename)
820 g_free (hlpfile);
822 if (filedata == NULL)
823 return;
825 translate_file (filedata);
827 g_free (filedata);
829 if (!data)
830 return;
832 if (!node || !*node)
833 node = "[main]";
835 if (!(main_node = search_string (data, node))) {
836 message (D_ERROR, MSG_ERROR, _(" Cannot find node %s in help file "),
837 node);
839 /* Fallback to [main], return if it also cannot be found */
840 main_node = search_string (data, "[main]");
841 if (!main_node) {
842 interactive_display_finish ();
843 return;
847 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
849 whelp =
850 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
851 dialog_colors, help_callback, "[Help]", _("Help"),
852 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
854 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
855 currentpoint = main_node + 1; /* Skip the newline following the start of the node */
857 for (history_ptr = HISTORY_SIZE; history_ptr;) {
858 history_ptr--;
859 history[history_ptr].page = currentpoint;
860 history[history_ptr].link = selected_item;
863 help_bar = buttonbar_new (1);
864 ((Widget *) help_bar)->y -= whelp->y;
865 ((Widget *) help_bar)->x -= whelp->x;
867 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
869 add_widget (whelp, md);
870 add_widget (whelp, help_bar);
872 buttonbar_set_label_data (whelp, 1, _("Help"), help_help_cmd, whelp);
873 buttonbar_set_label_data (whelp, 2, _("Index"), help_index_cmd, whelp);
874 buttonbar_set_label_data (whelp, 3, _("Prev"), prev_node_cmd, whelp);
875 buttonbar_clear_label (whelp, 4);
876 buttonbar_clear_label (whelp, 5);
877 buttonbar_clear_label (whelp, 6);
878 buttonbar_clear_label (whelp, 7);
879 buttonbar_clear_label (whelp, 8);
880 buttonbar_clear_label (whelp, 9);
881 buttonbar_set_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
883 run_dlg (whelp);
884 interactive_display_finish ();
885 destroy_dlg (whelp);