root/src/help.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. search_string
  2. search_string_node
  3. search_char_node
  4. move_forward2
  5. move_backward2
  6. move_forward
  7. move_backward
  8. move_to_top
  9. move_to_bottom
  10. help_follow_link
  11. select_next_link
  12. select_prev_link
  13. start_link_area
  14. end_link_area
  15. clear_link_areas
  16. help_print_word
  17. help_show
  18. help_help
  19. help_index
  20. help_back
  21. help_next_link
  22. help_prev_link
  23. help_next_node
  24. help_prev_node
  25. help_select_link
  26. help_execute_cmd
  27. help_handle_key
  28. help_bg_callback
  29. help_resize
  30. help_callback
  31. interactive_display_finish
  32. translate_file
  33. md_callback
  34. help_mouse_callback
  35. mousedispatch_new
  36. help_interactive_display

   1 /*
   2    Hypertext file browser.
   3 
   4    Copyright (C) 1994-2020
   5    Free Software Foundation, Inc.
   6 
   7    This file is part of the Midnight Commander.
   8 
   9    The Midnight Commander is free software: you can redistribute it
  10    and/or modify it under the terms of the GNU General Public License as
  11    published by the Free Software Foundation, either version 3 of the License,
  12    or (at your option) any later version.
  13 
  14    The Midnight Commander is distributed in the hope that it will be useful,
  15    but WITHOUT ANY WARRANTY; without even the implied warranty of
  16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17    GNU General Public License for more details.
  18 
  19    You should have received a copy of the GNU General Public License
  20    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  21  */
  22 
  23 
  24 /** \file help.c
  25  *  \brief Source: hypertext file browser
  26  *
  27  *  Implements the hypertext file viewer.
  28  *  The hypertext file is a file that may have one or more nodes.  Each
  29  *  node ends with a ^D character and starts with a bracket, then the
  30  *  name of the node and then a closing bracket. Right after the closing
  31  *  bracket a newline is placed. This newline is not to be displayed by
  32  *  the help viewer and must be skipped - its sole purpose is to faciliate
  33  *  the work of the people managing the help file template (xnc.hlp) .
  34  *
  35  *  Links in the hypertext file are specified like this: the text that
  36  *  will be highlighted should have a leading ^A, then it comes the
  37  *  text, then a ^B indicating that highlighting is done, then the name
  38  *  of the node you want to link to and then a ^C.
  39  *
  40  *  The file must contain a ^D at the beginning and at the end of the
  41  *  file or the program will not be able to detect the end of file.
  42  *
  43  *  Lazyness/widgeting attack: This file does use the dialog manager
  44  *  and uses mainly the dialog to achieve the help work.  there is only
  45  *  one specialized widget and it's only used to forward the mouse messages
  46  *  to the appropriate routine.
  47  */
  48 
  49 
  50 #include <config.h>
  51 
  52 #include <errno.h>
  53 #include <limits.h>             /* MB_LEN_MAX */
  54 #include <stdio.h>
  55 #include <sys/types.h>
  56 #include <sys/stat.h>
  57 
  58 #include "lib/global.h"
  59 
  60 #include "lib/tty/tty.h"
  61 #include "lib/skin.h"
  62 #include "lib/strutil.h"
  63 #include "lib/fileloc.h"
  64 #include "lib/util.h"
  65 #include "lib/widget.h"
  66 #include "lib/event-types.h"
  67 
  68 #include "keybind-defaults.h"
  69 #include "help.h"
  70 
  71 /*** global variables ****************************************************************************/
  72 
  73 /*** file scope macro definitions ****************************************************************/
  74 
  75 #define MAXLINKNAME 80
  76 #define HISTORY_SIZE 20
  77 #define HELP_WINDOW_WIDTH MIN(80, COLS - 16)
  78 
  79 #define STRING_LINK_START       "\01"
  80 #define STRING_LINK_POINTER     "\02"
  81 #define STRING_LINK_END         "\03"
  82 #define STRING_NODE_END         "\04"
  83 
  84 /*** file scope type declarations ****************************************************************/
  85 
  86 /* Link areas for the mouse */
  87 typedef struct Link_Area
  88 {
  89     int x1, y1, x2, y2;
  90     const char *link_name;
  91 } Link_Area;
  92 
  93 /*** file scope variables ************************************************************************/
  94 
  95 static char *fdata = NULL;      /* Pointer to the loaded data file */
  96 static int help_lines;          /* Lines in help viewer */
  97 static int history_ptr;         /* For the history queue */
  98 static const char *main_node;   /* The main node */
  99 static const char *last_shown = NULL;   /* Last byte shown in a screen */
 100 static gboolean end_of_node = FALSE;    /* Flag: the last character of the node shown? */
 101 static const char *currentpoint;
 102 static const char *selected_item;
 103 
 104 /* The widget variables */
 105 static WDialog *whelp;
 106 
 107 static struct
 108 {
 109     const char *page;           /* Pointer to the selected page */
 110     const char *link;           /* Pointer to the selected link */
 111 } history[HISTORY_SIZE];
 112 
 113 static GSList *link_area = NULL;
 114 static gboolean inside_link_area = FALSE;
 115 
 116 static cb_ret_t help_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
 117 
 118 /*** file scope functions ************************************************************************/
 119 /* --------------------------------------------------------------------------------------------- */
 120 
 121 /** returns the position where text was found in the start buffer 
 122  * or 0 if not found
 123  */
 124 static const char *
 125 search_string (const char *start, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 126 {
 127     const char *result = NULL;
 128     char *local_text = g_strdup (text);
 129     char *d = local_text;
 130     const char *e = start;
 131 
 132     /* fmt sometimes replaces a space with a newline in the help file */
 133     /* Replace the newlines in the link name with spaces to correct the situation */
 134     while (*d != '\0')
 135     {
 136         if (*d == '\n')
 137             *d = ' ';
 138         str_next_char (&d);
 139     }
 140 
 141     /* Do search */
 142     for (d = local_text; *e; e++)
 143     {
 144         if (*d == *e)
 145             d++;
 146         else
 147             d = local_text;
 148         if (*d == '\0')
 149         {
 150             result = e + 1;
 151             break;
 152         }
 153     }
 154 
 155     g_free (local_text);
 156     return result;
 157 }
 158 
 159 /* --------------------------------------------------------------------------------------------- */
 160 /** Searches text in the buffer pointed by start.  Search ends
 161  * if the CHAR_NODE_END is found in the text.
 162  * @return NULL on failure
 163  */
 164 
 165 static const char *
 166 search_string_node (const char *start, const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 167 {
 168     const char *d = text;
 169     const char *e = start;
 170 
 171     if (start != NULL)
 172         for (; *e && *e != CHAR_NODE_END; e++)
 173         {
 174             if (*d == *e)
 175                 d++;
 176             else
 177                 d = text;
 178             if (*d == '\0')
 179                 return e + 1;
 180         }
 181 
 182     return NULL;
 183 }
 184 
 185 /* --------------------------------------------------------------------------------------------- */
 186 /** Searches the_char in the buffer pointer by start and searches
 187  * it can search forward (direction = 1) or backward (direction = -1)
 188  */
 189 
 190 static const char *
 191 search_char_node (const char *start, char the_char, int direction)
     /* [previous][next][first][last][top][bottom][index][help]  */
 192 {
 193     const char *e;
 194 
 195     for (e = start; (*e != '\0') && (*e != CHAR_NODE_END); e += direction)
 196         if (*e == the_char)
 197             return e;
 198 
 199     return NULL;
 200 }
 201 
 202 /* --------------------------------------------------------------------------------------------- */
 203 /** Returns the new current pointer when moved lines lines */
 204 
 205 static const char *
 206 move_forward2 (const char *c, int lines)
     /* [previous][next][first][last][top][bottom][index][help]  */
 207 {
 208     const char *p;
 209     int line;
 210 
 211     currentpoint = c;
 212     for (line = 0, p = currentpoint; (*p != '\0') && (*p != CHAR_NODE_END); str_cnext_char (&p))
 213     {
 214         if (line == lines)
 215             return currentpoint = p;
 216 
 217         if (*p == '\n')
 218             line++;
 219     }
 220     return currentpoint = c;
 221 }
 222 
 223 /* --------------------------------------------------------------------------------------------- */
 224 
 225 static const char *
 226 move_backward2 (const char *c, int lines)
     /* [previous][next][first][last][top][bottom][index][help]  */
 227 {
 228     const char *p;
 229     int line;
 230 
 231     currentpoint = c;
 232     for (line = 0, p = currentpoint; (*p != '\0') && ((int) (p - fdata) >= 0); str_cprev_char (&p))
 233     {
 234         if (*p == CHAR_NODE_END)
 235         {
 236             /* We reached the beginning of the node */
 237             /* Skip the node headers */
 238             while (*p != ']')
 239                 str_cnext_char (&p);
 240             return currentpoint = p + 2;        /* Skip the newline following the start of the node */
 241         }
 242 
 243         if (*(p - 1) == '\n')
 244             line++;
 245         if (line == lines)
 246             return currentpoint = p;
 247     }
 248     return currentpoint = c;
 249 }
 250 
 251 /* --------------------------------------------------------------------------------------------- */
 252 
 253 static void
 254 move_forward (int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 255 {
 256     if (!end_of_node)
 257         currentpoint = move_forward2 (currentpoint, i);
 258 }
 259 
 260 /* --------------------------------------------------------------------------------------------- */
 261 
 262 static void
 263 move_backward (int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 264 {
 265     currentpoint = move_backward2 (currentpoint, ++i);
 266 }
 267 
 268 /* --------------------------------------------------------------------------------------------- */
 269 
 270 static void
 271 move_to_top (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 272 {
 273     while (((int) (currentpoint > fdata) > 0) && (*currentpoint != CHAR_NODE_END))
 274         currentpoint--;
 275 
 276     while (*currentpoint != ']')
 277         currentpoint++;
 278     currentpoint = currentpoint + 2;    /* Skip the newline following the start of the node */
 279     selected_item = NULL;
 280 }
 281 
 282 /* --------------------------------------------------------------------------------------------- */
 283 
 284 static void
 285 move_to_bottom (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 286 {
 287     while ((*currentpoint != '\0') && (*currentpoint != CHAR_NODE_END))
 288         currentpoint++;
 289     currentpoint--;
 290     move_backward (1);
 291 }
 292 
 293 /* --------------------------------------------------------------------------------------------- */
 294 
 295 static const char *
 296 help_follow_link (const char *start, const char *lc_selected_item)
     /* [previous][next][first][last][top][bottom][index][help]  */
 297 {
 298     const char *p;
 299 
 300     if (lc_selected_item == NULL)
 301         return start;
 302 
 303     for (p = lc_selected_item; *p && *p != CHAR_NODE_END && *p != CHAR_LINK_POINTER; p++)
 304         ;
 305     if (*p == CHAR_LINK_POINTER)
 306     {
 307         int i;
 308         char link_name[MAXLINKNAME];
 309 
 310         link_name[0] = '[';
 311         for (i = 1; *p != CHAR_LINK_END && *p && *p != CHAR_NODE_END && i < MAXLINKNAME - 3;)
 312             link_name[i++] = *++p;
 313         link_name[i - 1] = ']';
 314         link_name[i] = '\0';
 315         p = search_string (fdata, link_name);
 316         if (p != NULL)
 317         {
 318             p += 1;             /* Skip the newline following the start of the node */
 319             return p;
 320         }
 321     }
 322 
 323     /* Create a replacement page with the error message */
 324     return _("Help file format error\n");
 325 }
 326 
 327 /* --------------------------------------------------------------------------------------------- */
 328 
 329 static const char *
 330 select_next_link (const char *current_link)
     /* [previous][next][first][last][top][bottom][index][help]  */
 331 {
 332     const char *p;
 333 
 334     if (current_link == NULL)
 335         return NULL;
 336 
 337     p = search_string_node (current_link, STRING_LINK_END);
 338     if (p == NULL)
 339         return NULL;
 340     p = search_string_node (p, STRING_LINK_START);
 341     if (p == NULL)
 342         return NULL;
 343     return p - 1;
 344 }
 345 
 346 /* --------------------------------------------------------------------------------------------- */
 347 
 348 static const char *
 349 select_prev_link (const char *current_link)
     /* [previous][next][first][last][top][bottom][index][help]  */
 350 {
 351     return current_link == NULL ? NULL : search_char_node (current_link - 1, CHAR_LINK_START, -1);
 352 }
 353 
 354 /* --------------------------------------------------------------------------------------------- */
 355 
 356 static void
 357 start_link_area (int x, int y, const char *link_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 358 {
 359     Link_Area *la;
 360 
 361     if (inside_link_area)
 362         message (D_NORMAL, _("Warning"), "%s", _("Internal bug: Double start of link area"));
 363 
 364     /* Allocate memory for a new link area */
 365     la = g_new (Link_Area, 1);
 366     /* Save the beginning coordinates of the link area */
 367     la->x1 = x;
 368     la->y1 = y;
 369     /* Save the name of the destination anchor */
 370     la->link_name = link_name;
 371     link_area = g_slist_prepend (link_area, la);
 372 
 373     inside_link_area = TRUE;
 374 }
 375 
 376 /* --------------------------------------------------------------------------------------------- */
 377 
 378 static void
 379 end_link_area (int x, int y)
     /* [previous][next][first][last][top][bottom][index][help]  */
 380 {
 381     if (inside_link_area)
 382     {
 383         Link_Area *la = (Link_Area *) link_area->data;
 384         /* Save the end coordinates of the link area */
 385         la->x2 = x;
 386         la->y2 = y;
 387         inside_link_area = FALSE;
 388     }
 389 }
 390 
 391 /* --------------------------------------------------------------------------------------------- */
 392 
 393 static void
 394 clear_link_areas (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 395 {
 396     g_clear_slist (&link_area, g_free);
 397     inside_link_area = FALSE;
 398 }
 399 
 400 /* --------------------------------------------------------------------------------------------- */
 401 
 402 static void
 403 help_print_word (WDialog * h, GString * word, int *col, int *line, gboolean add_space)
     /* [previous][next][first][last][top][bottom][index][help]  */
 404 {
 405     if (*line >= help_lines)
 406         g_string_set_size (word, 0);
 407     else
 408     {
 409         int w;
 410 
 411         w = str_term_width1 (word->str);
 412         if (*col + w >= HELP_WINDOW_WIDTH)
 413         {
 414             *col = 0;
 415             (*line)++;
 416         }
 417 
 418         if (*line >= help_lines)
 419             g_string_set_size (word, 0);
 420         else
 421         {
 422             widget_gotoyx (h, *line + 2, *col + 2);
 423             tty_print_string (word->str);
 424             g_string_set_size (word, 0);
 425             *col += w;
 426         }
 427     }
 428 
 429     if (add_space)
 430     {
 431         if (*col < HELP_WINDOW_WIDTH - 1)
 432         {
 433             tty_print_char (' ');
 434             (*col)++;
 435         }
 436         else
 437         {
 438             *col = 0;
 439             (*line)++;
 440         }
 441     }
 442 }
 443 
 444 /* --------------------------------------------------------------------------------------------- */
 445 
 446 static void
 447 help_show (WDialog * h, const char *paint_start)
     /* [previous][next][first][last][top][bottom][index][help]  */
 448 {
 449     const char *p, *n;
 450     int col, line, c;
 451     gboolean painting = TRUE;
 452     gboolean acs;               /* Flag: Alternate character set active? */
 453     gboolean repeat_paint;
 454     int active_col, active_line;        /* Active link position */
 455     char buff[MB_LEN_MAX + 1];
 456     GString *word;
 457 
 458     word = g_string_sized_new (32);
 459 
 460     tty_setcolor (HELP_NORMAL_COLOR);
 461     do
 462     {
 463         line = col = active_col = active_line = 0;
 464         repeat_paint = FALSE;
 465         acs = FALSE;
 466 
 467         clear_link_areas ();
 468         if ((int) (selected_item - paint_start) < 0)
 469             selected_item = NULL;
 470 
 471         p = paint_start;
 472         n = paint_start;
 473         while ((n[0] != '\0') && (n[0] != CHAR_NODE_END) && (line < help_lines))
 474         {
 475             p = n;
 476             n = str_cget_next_char (p);
 477             memcpy (buff, p, n - p);
 478             buff[n - p] = '\0';
 479 
 480             c = (unsigned char) buff[0];
 481             switch (c)
 482             {
 483             case CHAR_LINK_START:
 484                 if (selected_item == NULL)
 485                     selected_item = p;
 486                 if (p != selected_item)
 487                     tty_setcolor (HELP_LINK_COLOR);
 488                 else
 489                 {
 490                     tty_setcolor (HELP_SLINK_COLOR);
 491 
 492                     /* Store the coordinates of the link */
 493                     active_col = col + 2;
 494                     active_line = line + 2;
 495                 }
 496                 start_link_area (col, line, p);
 497                 break;
 498             case CHAR_LINK_POINTER:
 499                 painting = FALSE;
 500                 break;
 501             case CHAR_LINK_END:
 502                 painting = TRUE;
 503                 help_print_word (h, word, &col, &line, FALSE);
 504                 end_link_area (col - 1, line);
 505                 tty_setcolor (HELP_NORMAL_COLOR);
 506                 break;
 507             case CHAR_ALTERNATE:
 508                 acs = TRUE;
 509                 break;
 510             case CHAR_NORMAL:
 511                 acs = FALSE;
 512                 break;
 513             case CHAR_VERSION:
 514                 widget_gotoyx (h, line + 2, col + 2);
 515                 tty_print_string (VERSION);
 516                 col += str_term_width1 (VERSION);
 517                 break;
 518             case CHAR_FONT_BOLD:
 519                 tty_setcolor (HELP_BOLD_COLOR);
 520                 break;
 521             case CHAR_FONT_ITALIC:
 522                 tty_setcolor (HELP_ITALIC_COLOR);
 523                 break;
 524             case CHAR_FONT_NORMAL:
 525                 help_print_word (h, word, &col, &line, FALSE);
 526                 tty_setcolor (HELP_NORMAL_COLOR);
 527                 break;
 528             case '\n':
 529                 if (painting)
 530                     help_print_word (h, word, &col, &line, FALSE);
 531                 line++;
 532                 col = 0;
 533                 break;
 534             case ' ':
 535             case '\t':
 536                 /* word delimiter */
 537                 if (painting)
 538                 {
 539                     help_print_word (h, word, &col, &line, c == ' ');
 540                     if (c == '\t')
 541                     {
 542                         col = (col / 8 + 1) * 8;
 543                         if (col >= HELP_WINDOW_WIDTH)
 544                         {
 545                             line++;
 546                             col = 8;
 547                         }
 548                     }
 549                 }
 550                 break;
 551             default:
 552                 if (painting && (line < help_lines))
 553                 {
 554                     if (!acs)
 555                         /* accumulate symbols in a word */
 556                         g_string_append (word, buff);
 557                     else if (col < HELP_WINDOW_WIDTH)
 558                     {
 559                         widget_gotoyx (h, line + 2, col + 2);
 560 
 561                         if ((c == ' ') || (c == '.'))
 562                             tty_print_char (c);
 563                         else
 564 #ifndef HAVE_SLANG
 565                             tty_print_char (acs_map[c]);
 566 #else
 567                             SLsmg_draw_object (WIDGET (h)->y + line + 2, WIDGET (h)->x + col + 2,
 568                                                c);
 569 #endif
 570                         col++;
 571                     }
 572                 }
 573             }
 574         }
 575 
 576         /* print last word */
 577         if (n[0] == CHAR_NODE_END)
 578             help_print_word (h, word, &col, &line, FALSE);
 579 
 580         last_shown = p;
 581         end_of_node = line < help_lines;
 582         tty_setcolor (HELP_NORMAL_COLOR);
 583         if ((int) (selected_item - last_shown) >= 0)
 584         {
 585             if ((link_area == NULL) || (link_area->data == NULL))
 586                 selected_item = NULL;
 587             else
 588             {
 589                 selected_item = ((Link_Area *) link_area->data)->link_name;
 590                 repeat_paint = TRUE;
 591             }
 592         }
 593     }
 594     while (repeat_paint);
 595 
 596     g_string_free (word, TRUE);
 597 
 598     /* Position the cursor over a nice link */
 599     if (active_col)
 600         widget_gotoyx (h, active_line, active_col);
 601 }
 602 
 603 /* --------------------------------------------------------------------------------------------- */
 604 /** show help */
 605 
 606 static void
 607 help_help (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 608 {
 609     const char *p;
 610 
 611     history_ptr = (history_ptr + 1) % HISTORY_SIZE;
 612     history[history_ptr].page = currentpoint;
 613     history[history_ptr].link = selected_item;
 614 
 615     p = search_string (fdata, "[How to use help]");
 616     if (p != NULL)
 617     {
 618         currentpoint = p + 1;   /* Skip the newline following the start of the node */
 619         selected_item = NULL;
 620         widget_draw (WIDGET (h));
 621     }
 622 }
 623 
 624 /* --------------------------------------------------------------------------------------------- */
 625 
 626 static void
 627 help_index (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 628 {
 629     const char *new_item;
 630 
 631     new_item = search_string (fdata, "[Contents]");
 632 
 633     if (new_item == NULL)
 634         message (D_ERROR, MSG_ERROR, _("Cannot find node %s in help file"), "[Contents]");
 635     else
 636     {
 637         history_ptr = (history_ptr + 1) % HISTORY_SIZE;
 638         history[history_ptr].page = currentpoint;
 639         history[history_ptr].link = selected_item;
 640 
 641         currentpoint = new_item + 1;    /* Skip the newline following the start of the node */
 642         selected_item = NULL;
 643         widget_draw (WIDGET (h));
 644     }
 645 }
 646 
 647 /* --------------------------------------------------------------------------------------------- */
 648 
 649 static void
 650 help_back (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 651 {
 652     currentpoint = history[history_ptr].page;
 653     selected_item = history[history_ptr].link;
 654     history_ptr--;
 655     if (history_ptr < 0)
 656         history_ptr = HISTORY_SIZE - 1;
 657 
 658     widget_draw (WIDGET (h));   /* FIXME: unneeded? */
 659 }
 660 
 661 /* --------------------------------------------------------------------------------------------- */
 662 
 663 static void
 664 help_next_link (gboolean move_down)
     /* [previous][next][first][last][top][bottom][index][help]  */
 665 {
 666     const char *new_item;
 667 
 668     new_item = select_next_link (selected_item);
 669     if (new_item != NULL)
 670     {
 671         selected_item = new_item;
 672         if ((int) (selected_item - last_shown) >= 0)
 673         {
 674             if (move_down)
 675                 move_forward (1);
 676             else
 677                 selected_item = NULL;
 678         }
 679     }
 680     else if (move_down)
 681         move_forward (1);
 682     else
 683         selected_item = NULL;
 684 }
 685 
 686 /* --------------------------------------------------------------------------------------------- */
 687 
 688 static void
 689 help_prev_link (gboolean move_up)
     /* [previous][next][first][last][top][bottom][index][help]  */
 690 {
 691     const char *new_item;
 692 
 693     new_item = select_prev_link (selected_item);
 694     selected_item = new_item;
 695     if ((selected_item == NULL) || (selected_item < currentpoint))
 696     {
 697         if (move_up)
 698             move_backward (1);
 699         else if ((link_area != NULL) && (link_area->data != NULL))
 700             selected_item = ((Link_Area *) link_area->data)->link_name;
 701         else
 702             selected_item = NULL;
 703     }
 704 }
 705 
 706 /* --------------------------------------------------------------------------------------------- */
 707 
 708 static void
 709 help_next_node (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 710 {
 711     const char *new_item;
 712 
 713     new_item = currentpoint;
 714     while ((*new_item != '\0') && (*new_item != CHAR_NODE_END))
 715         new_item++;
 716 
 717     if (*++new_item == '[')
 718         while (*++new_item != '\0')
 719             if ((*new_item == ']') && (*++new_item != '\0') && (*++new_item != '\0'))
 720             {
 721                 currentpoint = new_item;
 722                 selected_item = NULL;
 723                 break;
 724             }
 725 }
 726 
 727 /* --------------------------------------------------------------------------------------------- */
 728 
 729 static void
 730 help_prev_node (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 731 {
 732     const char *new_item;
 733 
 734     new_item = currentpoint;
 735     while (((int) (new_item - fdata) > 1) && (*new_item != CHAR_NODE_END))
 736         new_item--;
 737     new_item--;
 738     while (((int) (new_item - fdata) > 0) && (*new_item != CHAR_NODE_END))
 739         new_item--;
 740     while (*new_item != ']')
 741         new_item++;
 742     currentpoint = new_item + 2;
 743     selected_item = NULL;
 744 }
 745 
 746 /* --------------------------------------------------------------------------------------------- */
 747 
 748 static void
 749 help_select_link (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 750 {
 751     /* follow link */
 752     if (selected_item == NULL)
 753     {
 754 #ifdef WE_WANT_TO_GO_BACKWARD_ON_KEY_RIGHT
 755         /* Is there any reason why the right key would take us
 756          * backward if there are no links selected?, I agree
 757          * with Torben than doing nothing in this case is better
 758          */
 759         /* If there are no links, go backward in history */
 760         history_ptr--;
 761         if (history_ptr < 0)
 762             history_ptr = HISTORY_SIZE - 1;
 763 
 764         currentpoint = history[history_ptr].page;
 765         selected_item = history[history_ptr].link;
 766 #endif
 767     }
 768     else
 769     {
 770         history_ptr = (history_ptr + 1) % HISTORY_SIZE;
 771         history[history_ptr].page = currentpoint;
 772         history[history_ptr].link = selected_item;
 773         currentpoint = help_follow_link (currentpoint, selected_item);
 774     }
 775 
 776     selected_item = NULL;
 777 }
 778 
 779 /* --------------------------------------------------------------------------------------------- */
 780 
 781 static cb_ret_t
 782 help_execute_cmd (long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 783 {
 784     cb_ret_t ret = MSG_HANDLED;
 785 
 786     switch (command)
 787     {
 788     case CK_Help:
 789         help_help (whelp);
 790         break;
 791     case CK_Index:
 792         help_index (whelp);
 793         break;
 794     case CK_Back:
 795         help_back (whelp);
 796         break;
 797     case CK_Up:
 798         help_prev_link (TRUE);
 799         break;
 800     case CK_Down:
 801         help_next_link (TRUE);
 802         break;
 803     case CK_PageDown:
 804         move_forward (help_lines - 1);
 805         break;
 806     case CK_PageUp:
 807         move_backward (help_lines - 1);
 808         break;
 809     case CK_HalfPageDown:
 810         move_forward (help_lines / 2);
 811         break;
 812     case CK_HalfPageUp:
 813         move_backward (help_lines / 2);
 814         break;
 815     case CK_Top:
 816         move_to_top ();
 817         break;
 818     case CK_Bottom:
 819         move_to_bottom ();
 820         break;
 821     case CK_Enter:
 822         help_select_link ();
 823         break;
 824     case CK_LinkNext:
 825         help_next_link (FALSE);
 826         break;
 827     case CK_LinkPrev:
 828         help_prev_link (FALSE);
 829         break;
 830     case CK_NodeNext:
 831         help_next_node ();
 832         break;
 833     case CK_NodePrev:
 834         help_prev_node ();
 835         break;
 836     case CK_Quit:
 837         dlg_stop (whelp);
 838         break;
 839     default:
 840         ret = MSG_NOT_HANDLED;
 841     }
 842 
 843     return ret;
 844 }
 845 
 846 /* --------------------------------------------------------------------------------------------- */
 847 
 848 static cb_ret_t
 849 help_handle_key (WDialog * h, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 850 {
 851     Widget *w = WIDGET (h);
 852     long command;
 853 
 854     command = widget_lookup_key (w, key);
 855     if ((command == CK_IgnoreKey) || (help_execute_cmd (command) == MSG_NOT_HANDLED))
 856         return MSG_NOT_HANDLED;
 857 
 858     widget_draw (w);
 859 
 860     return MSG_HANDLED;
 861 }
 862 
 863 /* --------------------------------------------------------------------------------------------- */
 864 
 865 static cb_ret_t
 866 help_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 867 {
 868     switch (msg)
 869     {
 870     case MSG_DRAW:
 871         frame_callback (w, NULL, MSG_DRAW, 0, NULL);
 872         help_show (DIALOG (w->owner), currentpoint);
 873         return MSG_HANDLED;
 874 
 875     default:
 876         return frame_callback (w, sender, msg, parm, data);
 877     }
 878 }
 879 
 880 /* --------------------------------------------------------------------------------------------- */
 881 
 882 static cb_ret_t
 883 help_resize (WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
 884 {
 885     Widget *w = WIDGET (h);
 886     WButtonBar *bb;
 887     WRect r;
 888 
 889     help_lines = MIN (LINES - 4, MAX (2 * LINES / 3, 18));
 890     rect_init (&r, w->y, w->x, help_lines + 4, HELP_WINDOW_WIDTH + 4);
 891     dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
 892     bb = find_buttonbar (h);
 893     widget_set_size (WIDGET (bb), LINES - 1, 0, 1, COLS);
 894 
 895     return MSG_HANDLED;
 896 }
 897 
 898 /* --------------------------------------------------------------------------------------------- */
 899 
 900 static cb_ret_t
 901 help_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 902 {
 903     WDialog *h = DIALOG (w);
 904 
 905     switch (msg)
 906     {
 907     case MSG_RESIZE:
 908         return help_resize (h);
 909 
 910     case MSG_KEY:
 911         return help_handle_key (h, parm);
 912 
 913     case MSG_ACTION:
 914         /* Handle shortcuts and buttonbar. */
 915         return help_execute_cmd (parm);
 916 
 917     default:
 918         return dlg_default_callback (w, sender, msg, parm, data);
 919     }
 920 }
 921 
 922 /* --------------------------------------------------------------------------------------------- */
 923 
 924 static void
 925 interactive_display_finish (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 926 {
 927     clear_link_areas ();
 928 }
 929 
 930 /* --------------------------------------------------------------------------------------------- */
 931 /** translate help file into terminal encoding */
 932 
 933 static void
 934 translate_file (char *filedata)
     /* [previous][next][first][last][top][bottom][index][help]  */
 935 {
 936     GIConv conv;
 937     GString *translated_data;
 938 
 939     /* initial allocation for largest whole help file */
 940     translated_data = g_string_sized_new (32 * 1024);
 941 
 942     conv = str_crt_conv_from ("UTF-8");
 943 
 944     if (conv == INVALID_CONV)
 945         g_string_free (translated_data, TRUE);
 946     else
 947     {
 948         g_free (fdata);
 949 
 950         if (str_convert (conv, filedata, translated_data) != ESTR_FAILURE)
 951             fdata = g_string_free (translated_data, FALSE);
 952         else
 953         {
 954             fdata = NULL;
 955             g_string_free (translated_data, TRUE);
 956         }
 957         str_close_conv (conv);
 958     }
 959 }
 960 
 961 /* --------------------------------------------------------------------------------------------- */
 962 
 963 static cb_ret_t
 964 md_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 965 {
 966     switch (msg)
 967     {
 968     case MSG_RESIZE:
 969         widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
 970         w->lines = help_lines;
 971         return MSG_HANDLED;
 972 
 973     default:
 974         return widget_default_callback (w, sender, msg, parm, data);
 975     }
 976 }
 977 
 978 /* --------------------------------------------------------------------------------------------- */
 979 
 980 static void
 981 help_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 982 {
 983     int x, y;
 984     GSList *current_area;
 985 
 986     if (msg != MSG_MOUSE_CLICK)
 987         return;
 988 
 989     if ((event->buttons & GPM_B_RIGHT) != 0)
 990     {
 991         /* Right button click */
 992         help_back (whelp);
 993         return;
 994     }
 995 
 996     /* Left bytton click */
 997 
 998     /* The event is relative to the dialog window, adjust it: */
 999     x = event->x - 1;
1000     y = event->y - 1;
1001 
1002     /* Test whether the mouse click is inside one of the link areas */
1003     for (current_area = link_area; current_area != NULL; current_area = g_slist_next (current_area))
1004     {
1005         Link_Area *la = (Link_Area *) current_area->data;
1006 
1007         /* Test one line link area */
1008         if (y == la->y1 && x >= la->x1 && y == la->y2 && x <= la->x2)
1009             break;
1010 
1011         /* Test two line link area */
1012         if (la->y1 + 1 == la->y2)
1013         {
1014             /* The first line || The second line */
1015             if ((y == la->y1 && x >= la->x1) || (y == la->y2 && x <= la->x2))
1016                 break;
1017         }
1018         /* Mouse will not work with link areas of more than two lines */
1019     }
1020 
1021     /* Test whether a link area was found */
1022     if (current_area != NULL)
1023     {
1024         Link_Area *la = (Link_Area *) current_area->data;
1025 
1026         /* The click was inside a link area -> follow the link */
1027         history_ptr = (history_ptr + 1) % HISTORY_SIZE;
1028         history[history_ptr].page = currentpoint;
1029         history[history_ptr].link = la->link_name;
1030         currentpoint = help_follow_link (currentpoint, la->link_name);
1031         selected_item = NULL;
1032     }
1033     else if (y < 0)
1034         move_backward (help_lines - 1);
1035     else if (y >= help_lines)
1036         move_forward (help_lines - 1);
1037     else if (y < help_lines / 2)
1038         move_backward (1);
1039     else
1040         move_forward (1);
1041 
1042     /* Show the new node */
1043     widget_draw (WIDGET (w->owner));
1044 }
1045 
1046 /* --------------------------------------------------------------------------------------------- */
1047 
1048 static Widget *
1049 mousedispatch_new (int y, int x, int yl, int xl)
     /* [previous][next][first][last][top][bottom][index][help]  */
1050 {
1051     Widget *w;
1052 
1053     w = g_new0 (Widget, 1);
1054     widget_init (w, y, x, yl, xl, md_callback, help_mouse_callback);
1055     w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
1056 
1057     return w;
1058 }
1059 
1060 /* --------------------------------------------------------------------------------------------- */
1061 /*** public functions ****************************************************************************/
1062 /* --------------------------------------------------------------------------------------------- */
1063 
1064 /* event callback */
1065 gboolean
1066 help_interactive_display (const gchar * event_group_name, const gchar * event_name,
     /* [previous][next][first][last][top][bottom][index][help]  */
1067                           gpointer init_data, gpointer data)
1068 {
1069     const dlg_colors_t help_colors = {
1070         HELP_NORMAL_COLOR,      /* common text color */
1071         0,                      /* unused in help */
1072         HELP_BOLD_COLOR,        /* bold text color */
1073         0,                      /* unused in help */
1074         HELP_TITLE_COLOR        /* title color */
1075     };
1076 
1077     Widget *wh;
1078     WGroup *g;
1079     WButtonBar *help_bar;
1080     Widget *md;
1081     char *hlpfile = NULL;
1082     char *filedata;
1083     ev_help_t *event_data = (ev_help_t *) data;
1084 
1085     (void) event_group_name;
1086     (void) event_name;
1087     (void) init_data;
1088 
1089     if (event_data->filename != NULL)
1090         g_file_get_contents (event_data->filename, &filedata, NULL, NULL);
1091     else
1092         filedata = load_mc_home_file (mc_global.share_data_dir, MC_HELP, &hlpfile, NULL);
1093 
1094     if (filedata == NULL)
1095         message (D_ERROR, MSG_ERROR, _("Cannot open file %s\n%s"),
1096                  event_data->filename ? event_data->filename : hlpfile, unix_error_string (errno));
1097 
1098     g_free (hlpfile);
1099 
1100     if (filedata == NULL)
1101         return TRUE;
1102 
1103     translate_file (filedata);
1104 
1105     g_free (filedata);
1106 
1107     if (fdata == NULL)
1108         return TRUE;
1109 
1110     if ((event_data->node == NULL) || (*event_data->node == '\0'))
1111         event_data->node = "[main]";
1112 
1113     main_node = search_string (fdata, event_data->node);
1114 
1115     if (main_node == NULL)
1116     {
1117         message (D_ERROR, MSG_ERROR, _("Cannot find node %s in help file"), event_data->node);
1118 
1119         /* Fallback to [main], return if it also cannot be found */
1120         main_node = search_string (fdata, "[main]");
1121         if (main_node == NULL)
1122         {
1123             interactive_display_finish ();
1124             return TRUE;
1125         }
1126     }
1127 
1128     help_lines = MIN (LINES - 4, MAX (2 * LINES / 3, 18));
1129 
1130     whelp =
1131         dlg_create (TRUE, 0, 0, help_lines + 4, HELP_WINDOW_WIDTH + 4, WPOS_CENTER | WPOS_TRYUP,
1132                     FALSE, help_colors, help_callback, NULL, "[Help]", _("Help"));
1133     wh = WIDGET (whelp);
1134     g = GROUP (whelp);
1135     wh->keymap = help_map;
1136     widget_want_tab (wh, TRUE);
1137     /* draw background */
1138     whelp->bg->callback = help_bg_callback;
1139 
1140     selected_item = search_string_node (main_node, STRING_LINK_START) - 1;
1141     currentpoint = main_node + 1;       /* Skip the newline following the start of the node */
1142 
1143     for (history_ptr = HISTORY_SIZE; history_ptr;)
1144     {
1145         history_ptr--;
1146         history[history_ptr].page = currentpoint;
1147         history[history_ptr].link = selected_item;
1148     }
1149 
1150     help_bar = buttonbar_new (TRUE);
1151     WIDGET (help_bar)->y -= wh->y;
1152     WIDGET (help_bar)->x -= wh->x;
1153 
1154     md = mousedispatch_new (1, 1, help_lines, HELP_WINDOW_WIDTH - 2);
1155 
1156     group_add_widget (g, md);
1157     group_add_widget (g, help_bar);
1158 
1159     buttonbar_set_label (help_bar, 1, Q_ ("ButtonBar|Help"), wh->keymap, NULL);
1160     buttonbar_set_label (help_bar, 2, Q_ ("ButtonBar|Index"), wh->keymap, NULL);
1161     buttonbar_set_label (help_bar, 3, Q_ ("ButtonBar|Prev"), wh->keymap, NULL);
1162     buttonbar_set_label (help_bar, 4, "", wh->keymap, NULL);
1163     buttonbar_set_label (help_bar, 5, "", wh->keymap, NULL);
1164     buttonbar_set_label (help_bar, 6, "", wh->keymap, NULL);
1165     buttonbar_set_label (help_bar, 7, "", wh->keymap, NULL);
1166     buttonbar_set_label (help_bar, 8, "", wh->keymap, NULL);
1167     buttonbar_set_label (help_bar, 9, "", wh->keymap, NULL);
1168     buttonbar_set_label (help_bar, 10, Q_ ("ButtonBar|Quit"), wh->keymap, NULL);
1169 
1170     dlg_run (whelp);
1171     interactive_display_finish ();
1172     dlg_destroy (whelp);
1173     return TRUE;
1174 }
1175 
1176 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */