utilunix.c(mc_tmpdir): Check return value of getpwuid() for NULL.
[midnight-commander.git] / src / help.c
blob95665d21119b1095949440a80ddc3b04e78728bb
1 /* Hypertext file browser.
2 Copyright (C) 1994, 1995 Miguel de Icaza.
3 Copyright (C) 1994, 1995 Janne Kukonlehto
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 Implements the hypertext file viewer.
20 The hypertext file is a file that may have one or more nodes. Each
21 node ends with a ^D character and starts with a bracket, then the
22 name of the node and then a closing bracket.
24 Links in the hypertext file are specified like this: the text that
25 will be highlighted should have a leading ^A, then it comes the
26 text, then a ^B indicating that highlighting is done, then the name
27 of the node you want to link to and then a ^C.
29 The file must contain a ^D at the beginning and at the end of the
30 file or the program will not be able to detect the end of file.
32 Lazyness/widgeting attack: This file does use the dialog manager
33 and uses mainly the dialog to achieve the help work. there is only
34 one specialized widget and it's only used to forward the mouse messages
35 to the appropiate routine.
39 #include <config.h>
40 #include <stdio.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <errno.h>
45 #include "global.h"
46 #include "tty.h"
47 #include "color.h"
48 #include "win.h"
49 #include "mouse.h"
50 #include "key.h" /* For mi_getch() */
51 #include "help.h"
52 #include "dialog.h" /* For Dlg_head */
53 #include "widget.h" /* For Widget */
54 #include "wtools.h" /* For common_dialog_repaint() */
56 #define MAXLINKNAME 80
57 #define HISTORY_SIZE 20
58 #define HELP_WINDOW_WIDTH (HELP_TEXT_WIDTH + 4)
60 #define STRING_LINK_START "\01"
61 #define STRING_LINK_POINTER "\02"
62 #define STRING_LINK_END "\03"
63 #define STRING_NODE_END "\04"
65 static char *data; /* Pointer to the loaded data file */
66 static int help_lines; /* Lines in help viewer */
67 static int history_ptr; /* For the history queue */
68 static char *main_node; /* The main node */
69 static char *last_shown = 0; /* Last byte shown in a screen */
70 static int end_of_node = 0; /* Flag: the last character of the node shown? */
71 static char *currentpoint, *startpoint;
72 static char *selected_item;
74 /* The widget variables */
75 static Dlg_head *whelp;
77 static struct {
78 char *page; /* Pointer to the selected page */
79 char *link; /* Pointer to the selected link */
80 } history [HISTORY_SIZE];
82 /* Link areas for the mouse */
83 typedef struct Link_Area {
84 int x1, y1, x2, y2;
85 char *link_name;
86 struct Link_Area *next;
87 } Link_Area;
89 static Link_Area *link_area = NULL;
90 static int inside_link_area = 0;
92 static cb_ret_t help_callback (struct Dlg_head *h, dlg_msg_t, int parm);
94 #ifdef HAS_ACS_AS_PCCHARS
95 static const struct {
96 int acscode;
97 int pccode;
98 } acs2pc_table [] = {
99 { 'q', 0xC4 },
100 { 'x', 0xB3 },
101 { 'l', 0xDA },
102 { 'k', 0xBF },
103 { 'm', 0xC0 },
104 { 'j', 0xD9 },
105 { 'a', 0xB0 },
106 { 'u', 0xB4 },
107 { 't', 0xC3 },
108 { 'w', 0xC2 },
109 { 'v', 0xC1 },
110 { 'n', 0xC5 },
111 { 0, 0 } };
113 static int acs2pc (int acscode)
115 int i;
117 for (i = 0; acs2pc_table[i].acscode != 0; i++)
118 if (acscode == acs2pc_table[i].acscode) {
119 return acs2pc_table[i].pccode;
121 return 0;
123 #endif
125 /* returns the position where text was found in the start buffer */
126 /* or 0 if not found */
127 static char *
128 search_string (char *start, char *text)
130 char *d = text;
131 char *e = start;
133 /* fmt sometimes replaces a space with a newline in the help file */
134 /* Replace the newlines in the link name with spaces to correct the situation */
135 while (*d){
136 if (*d == '\n')
137 *d = ' ';
138 d++;
140 /* Do search */
141 for (d = text; *e; e++){
142 if (*d == *e)
143 d++;
144 else
145 d = text;
146 if (!*d)
147 return e+1;
149 return 0;
152 /* Searches text in the buffer pointed by start. Search ends */
153 /* if the CHAR_NODE_END is found in the text. Returns 0 on failure */
154 static char *search_string_node (char *start, char *text)
156 char *d = text;
157 char *e = start;
159 if (!start)
160 return 0;
162 for (; *e && *e != CHAR_NODE_END; e++){
163 if (*d == *e)
164 d++;
165 else
166 d = text;
167 if (!*d)
168 return e+1;
170 return 0;
173 /* Searches the_char in the buffer pointer by start and searches */
174 /* it can search forward (direction = 1) or backward (direction = -1) */
175 static char *search_char_node (char *start, char the_char, int direction)
177 char *e;
179 e = start;
181 for (; *e && (*e != CHAR_NODE_END); e += direction){
182 if (*e == the_char)
183 return e;
185 return 0;
188 /* Returns the new current pointer when moved lines lines */
189 static char *move_forward2 (char *c, int lines)
191 char *p;
192 int line;
194 currentpoint = c;
195 for (line = 0, p = currentpoint; *p && *p != CHAR_NODE_END; p++){
196 if (line == lines)
197 return currentpoint = p;
198 if (*p == '\n')
199 line++;
201 return currentpoint = c;
204 static char *move_backward2 (char *c, int lines)
206 char *p;
207 int line;
209 currentpoint = c;
210 for (line = 0, p = currentpoint; *p && p >= data; p--){
211 if (*p == CHAR_NODE_END)
213 /* We reached the beginning of the node */
214 /* Skip the node headers */
215 while (*p != ']') p++;
216 return currentpoint = p + 2;
218 if (*(p - 1) == '\n')
219 line++;
220 if (line == lines)
221 return currentpoint = p;
223 return currentpoint = c;
226 static void move_forward (int i)
228 if (end_of_node)
229 return;
230 currentpoint = move_forward2 (currentpoint, i);
233 static void move_backward (int i)
235 currentpoint = move_backward2 (currentpoint, ++i);
238 static void move_to_top (int dummy)
240 while (currentpoint > data && *currentpoint != CHAR_NODE_END)
241 currentpoint--;
242 while (*currentpoint != ']')
243 currentpoint++;
244 currentpoint = currentpoint + 1;
245 selected_item = NULL;
248 static void move_to_bottom (int dummy)
250 while (*currentpoint && *currentpoint != CHAR_NODE_END)
251 currentpoint++;
252 currentpoint--;
253 move_backward (help_lines - 1);
256 static char *help_follow_link (char *start, char *selected_item)
258 char link_name [MAXLINKNAME];
259 char *p;
260 int i = 0;
262 if (!selected_item)
263 return start;
265 for (p = selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
267 if (*p == CHAR_LINK_POINTER){
268 link_name [0] = '[';
269 for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME-3; )
270 link_name [i++] = *++p;
271 link_name [i-1] = ']';
272 link_name [i] = 0;
273 p = search_string (data, link_name);
274 if (p)
275 return p;
278 /* Create a replacement page with the error message */
279 return _(" Help file format error\n");
282 static char *select_next_link (char *start, char *current_link)
284 char *p;
286 if (!current_link)
287 return 0;
289 p = search_string_node (current_link, STRING_LINK_END);
290 if (!p)
291 return NULL;
292 p = search_string_node (p, STRING_LINK_START);
293 if (!p)
294 return NULL;
295 return p - 1;
298 static char *select_prev_link (char *start, char *current_link)
300 char *p;
302 if (!current_link)
303 return 0;
305 p = current_link - 1;
306 if (p <= start)
307 return 0;
309 p = search_char_node (p, CHAR_LINK_START, -1);
310 return p;
313 static void start_link_area (int x, int y, char *link_name)
315 Link_Area *new;
317 if (inside_link_area)
318 message (0, _("Warning"), _(" Internal bug: Double start of link area "));
320 /* Allocate memory for a new link area */
321 new = g_new (Link_Area, 1);
322 new->next = link_area;
323 link_area = new;
325 /* Save the beginning coordinates of the link area */
326 link_area->x1 = x;
327 link_area->y1 = y;
329 /* Save the name of the destination anchor */
330 link_area->link_name = link_name;
332 inside_link_area = 1;
335 static void end_link_area (int x, int y)
337 if (inside_link_area){
338 /* Save the end coordinates of the link area */
339 link_area->x2 = x;
340 link_area->y2 = y;
342 inside_link_area = 0;
346 static void clear_link_areas (void)
348 Link_Area *current;
350 while (link_area){
351 current = link_area;
352 link_area = current -> next;
353 g_free (current);
355 inside_link_area = 0;
358 static void help_show (Dlg_head *h, char *paint_start)
360 char *p;
361 int col, line, c;
362 int painting = 1;
363 int acs; /* Flag: Alternate character set active? */
364 int repeat_paint;
365 int active_col, active_line;/* Active link position */
367 attrset (HELP_NORMAL_COLOR);
368 do {
370 line = col = acs = active_col = active_line = repeat_paint = 0;
372 clear_link_areas ();
373 if (selected_item < paint_start)
374 selected_item = NULL;
376 for (p = paint_start; *p && *p != CHAR_NODE_END && line < help_lines; p++) {
377 c = (unsigned char)*p;
378 switch (c){
379 case CHAR_LINK_START:
380 if (selected_item == NULL)
381 selected_item = p;
382 if (p == selected_item){
383 attrset (HELP_SLINK_COLOR);
385 /* Store the coordinates of the link */
386 active_col = col + 2;
387 active_line = line + 2;
389 else
390 attrset (HELP_LINK_COLOR);
391 start_link_area (col, line, p);
392 break;
393 case CHAR_LINK_POINTER:
394 painting = 0;
395 end_link_area (col - 1, line);
396 break;
397 case CHAR_LINK_END:
398 painting = 1;
399 attrset (HELP_NORMAL_COLOR);
400 break;
401 case CHAR_ALTERNATE:
402 acs = 1;
403 break;
404 case CHAR_NORMAL:
405 acs = 0;
406 break;
407 case CHAR_VERSION:
408 dlg_move (h, line+2, col+2);
409 addstr (VERSION);
410 col += strlen (VERSION);
411 break;
412 case CHAR_FONT_BOLD:
413 attrset (HELP_BOLD_COLOR);
414 break;
415 case CHAR_FONT_ITALIC:
416 attrset (HELP_ITALIC_COLOR);
417 break;
418 case CHAR_FONT_NORMAL:
419 attrset (HELP_NORMAL_COLOR);
420 break;
421 case '\n':
422 line++;
423 col = 0;
424 break;
425 case '\t':
426 col = (col/8 + 1) * 8;
427 break;
428 default:
429 if (!painting)
430 continue;
431 if (col > HELP_WINDOW_WIDTH-1)
432 continue;
434 dlg_move (h, line+2, col+2);
435 if (acs){
436 if (c == ' ' || c == '.')
437 addch (c);
438 else
439 #ifndef HAVE_SLANG
440 addch (acs_map [c]);
441 #else
442 SLsmg_draw_object (h->y + line + 2, h->x + col + 2, c);
443 #endif
444 } else
445 addch (c);
446 col++;
447 break;
450 last_shown = p;
451 end_of_node = line < help_lines;
452 attrset (HELP_NORMAL_COLOR);
453 if (selected_item >= last_shown){
454 if (link_area != NULL){
455 selected_item = link_area->link_name;
456 repeat_paint = 1;
458 else
459 selected_item = NULL;
461 } while (repeat_paint);
463 /* Position the cursor over a nice link */
464 if (active_col)
465 dlg_move (h, active_line, active_col);
468 static int help_event (Gpm_Event *event, Widget *w)
470 Link_Area *current_area;
472 if (! (event->type & GPM_UP))
473 return 0;
475 /* The event is relative to the dialog window, adjust it: */
476 event->y -= 2;
478 if (event->buttons & GPM_B_RIGHT){
479 currentpoint = startpoint = history [history_ptr].page;
480 selected_item = history [history_ptr].link;
481 history_ptr--;
482 if (history_ptr < 0)
483 history_ptr = HISTORY_SIZE-1;
485 help_callback (w->parent, DLG_DRAW, 0);
486 return 0;
489 /* Test whether the mouse click is inside one of the link areas */
490 current_area = link_area;
491 while (current_area)
493 /* Test one line link area */
494 if (event->y == current_area->y1 && event->x >= current_area->x1 &&
495 event->y == current_area->y2 && event->x <= current_area->x2)
496 break;
497 /* Test two line link area */
498 if (current_area->y1 + 1 == current_area->y2){
499 /* The first line */
500 if (event->y == current_area->y1 && event->x >= current_area->x1)
501 break;
502 /* The second line */
503 if (event->y == current_area->y2 && event->x <= current_area->x2)
504 break;
506 /* Mouse will not work with link areas of more than two lines */
508 current_area = current_area -> next;
511 /* Test whether a link area was found */
512 if (current_area){
513 /* The click was inside a link area -> follow the link */
514 history_ptr = (history_ptr+1) % HISTORY_SIZE;
515 history [history_ptr].page = currentpoint;
516 history [history_ptr].link = current_area->link_name;
517 currentpoint = startpoint = help_follow_link (currentpoint, current_area->link_name);
518 selected_item = NULL;
519 } else{
520 if (event->y < 0)
521 move_backward (help_lines - 1);
522 else if (event->y >= help_lines)
523 move_forward (help_lines - 1);
524 else if (event->y < help_lines/2)
525 move_backward (1);
526 else
527 move_forward (1);
530 /* Show the new node */
531 help_callback (w->parent, DLG_DRAW, 0);
533 return 0;
536 /* show help */
537 static void
538 help_help_cmd (Dlg_head *h)
540 history_ptr = (history_ptr+1) % HISTORY_SIZE;
541 history [history_ptr].page = currentpoint;
542 history [history_ptr].link = selected_item;
543 currentpoint = startpoint = search_string (data, "[How to use help]") + 1;
544 selected_item = NULL;
545 help_callback (h, DLG_DRAW, 0);
548 static void
549 help_index_cmd (Dlg_head * h)
551 char *new_item;
553 if (!(new_item = search_string (data, "[Contents]"))) {
554 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
555 "[Contents]");
556 return;
559 history_ptr = (history_ptr + 1) % HISTORY_SIZE;
560 history[history_ptr].page = currentpoint;
561 history[history_ptr].link = selected_item;
563 currentpoint = startpoint = new_item + 1;
564 selected_item = NULL;
565 help_callback (h, DLG_DRAW, 0);
568 static void help_quit_cmd (void *x)
570 dlg_stop ((Dlg_head *)x);
573 static void prev_node_cmd (Dlg_head *h)
575 currentpoint = startpoint = history [history_ptr].page;
576 selected_item = history [history_ptr].link;
577 history_ptr--;
578 if (history_ptr < 0)
579 history_ptr = HISTORY_SIZE-1;
581 help_callback (h, DLG_DRAW, 0);
584 static cb_ret_t
585 md_callback (Widget *w, widget_msg_t msg, int parm)
587 return default_proc (msg, parm);
590 static Widget *
591 mousedispatch_new (int y, int x, int yl, int xl)
593 Widget *w = g_new (Widget, 1);
595 init_widget (w, y, x, yl, xl, (callback_fn) md_callback,
596 (mouse_h) help_event);
598 return w;
601 static cb_ret_t
602 help_handle_key (struct Dlg_head *h, int c)
604 char *new_item;
606 if (c != KEY_UP && c != KEY_DOWN &&
607 check_movement_keys (c, help_lines, currentpoint,
608 (movefn) move_backward2,
609 (movefn) move_forward2,
610 (movefn) move_to_top,
611 (movefn) move_to_bottom))
612 /* Nothing */;
613 else switch (c){
614 case 'l':
615 case KEY_LEFT:
616 prev_node_cmd (h);
617 break;
619 case '\n':
620 case KEY_RIGHT:
621 /* follow link */
622 if (!selected_item){
623 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
624 /* Is there any reason why the right key would take us
625 * backward if there are no links selected?, I agree
626 * with Torben than doing nothing in this case is better
628 /* If there are no links, go backward in history */
629 history_ptr--;
630 if (history_ptr < 0)
631 history_ptr = HISTORY_SIZE-1;
633 currentpoint = startpoint = history [history_ptr].page;
634 selected_item = history [history_ptr].link;
635 #endif
636 } else {
637 history_ptr = (history_ptr+1) % HISTORY_SIZE;
638 history [history_ptr].page = currentpoint;
639 history [history_ptr].link = selected_item;
640 currentpoint = startpoint = help_follow_link (currentpoint, selected_item) + 1;
642 selected_item = NULL;
643 break;
645 case KEY_DOWN:
646 case '\t':
647 /* select next link */
648 new_item = select_next_link (startpoint, selected_item);
649 if (new_item){
650 selected_item = new_item;
651 if (selected_item >= last_shown){
652 if (c == KEY_DOWN)
653 move_forward (1);
654 else
655 selected_item = NULL;
657 } else if (c == KEY_DOWN)
658 move_forward (1);
659 else
660 selected_item = NULL;
661 break;
663 case KEY_UP:
664 case ALT ('\t'):
665 /* select previous link */
666 new_item = select_prev_link (startpoint, selected_item);
667 selected_item = new_item;
668 if (selected_item < currentpoint || selected_item >= last_shown){
669 if (c == KEY_UP)
670 move_backward (1);
671 else{
672 if (link_area != NULL)
673 selected_item = link_area->link_name;
674 else
675 selected_item = NULL;
678 break;
680 case 'n':
681 /* Next node */
682 new_item = currentpoint;
683 while (*new_item && *new_item != CHAR_NODE_END)
684 new_item++;
685 if (*++new_item == '['){
686 while (*++new_item) {
687 if (*new_item == ']' && *++new_item && *++new_item) {
688 currentpoint = new_item;
689 selected_item = NULL;
690 break;
694 break;
696 case 'p':
697 /* Previous node */
698 new_item = currentpoint;
699 while (new_item > data + 1 && *new_item != CHAR_NODE_END)
700 new_item--;
701 new_item--;
702 while (new_item > data && *new_item != CHAR_NODE_END)
703 new_item--;
704 while (*new_item != ']')
705 new_item++;
706 currentpoint = new_item + 2;
707 selected_item = NULL;
708 break;
710 case 'c':
711 help_index_cmd (h);
712 break;
714 case ESC_CHAR:
715 case XCTRL('g'):
716 dlg_stop (h);
717 break;
719 default:
720 return MSG_NOT_HANDLED;
723 help_callback (h, DLG_DRAW, 0);
724 return MSG_HANDLED;
727 static cb_ret_t
728 help_callback (struct Dlg_head *h, dlg_msg_t msg, int parm)
730 switch (msg) {
731 case DLG_DRAW:
732 common_dialog_repaint (h);
733 help_show (h, currentpoint);
734 return MSG_HANDLED;
736 case DLG_KEY:
737 return help_handle_key (h, parm);
739 default:
740 return default_dlg_callback (h, msg, parm);
744 static void
745 interactive_display_finish (void)
747 clear_link_areas ();
748 g_free (data);
751 void
752 interactive_display (char *filename, char *node)
754 WButtonBar *help_bar;
755 Widget *md;
756 char *hlpfile = filename;
758 if (filename)
759 data = load_file (filename);
760 else
761 data = load_mc_home_file ("mc.hlp", &hlpfile);
763 if (data == NULL) {
764 message (1, MSG_ERROR, _(" Cannot open file %s \n %s "), hlpfile,
765 unix_error_string (errno));
768 if (!filename)
769 g_free (hlpfile);
771 if (!data)
772 return;
774 if (!node || !*node)
775 node = "[main]";
777 if (!(main_node = search_string (data, node))) {
778 message (1, MSG_ERROR, _(" Cannot find node %s in help file "),
779 node);
781 /* Fallback to [main], return if it also cannot be found */
782 main_node = search_string (data, "[main]");
783 if (!main_node) {
784 interactive_display_finish ();
785 return;
789 help_lines = min (LINES - 4, max (2 * LINES / 3, 18));
791 whelp =
792 create_dlg (0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4,
793 dialog_colors, help_callback, "[Help]", _("Help"),
794 DLG_TRYUP | DLG_CENTER | DLG_WANT_TAB);
796 selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
797 currentpoint = startpoint = main_node + 1;
799 for (history_ptr = HISTORY_SIZE; history_ptr;) {
800 history_ptr--;
801 history[history_ptr].page = currentpoint;
802 history[history_ptr].link = selected_item;
805 help_bar = buttonbar_new (1);
806 help_bar->widget.y -= whelp->y;
807 help_bar->widget.x -= whelp->x;
809 md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
811 add_widget (whelp, md);
812 add_widget (whelp, help_bar);
814 define_label_data (whelp, 1, _("Help"), (buttonbarfn) help_help_cmd,
815 whelp);
816 define_label_data (whelp, 2, _("Index"), (buttonbarfn) help_index_cmd,
817 whelp);
818 define_label_data (whelp, 3, _("Prev"), (buttonbarfn) prev_node_cmd,
819 whelp);
820 define_label (whelp, 4, "", 0);
821 define_label (whelp, 5, "", 0);
822 define_label (whelp, 6, "", 0);
823 define_label (whelp, 7, "", 0);
824 define_label (whelp, 8, "", 0);
825 define_label (whelp, 9, "", 0);
826 define_label_data (whelp, 10, _("Quit"), help_quit_cmd, whelp);
828 run_dlg (whelp);
829 interactive_display_finish ();
830 destroy_dlg (whelp);