Manual pages: mcmcdiffmceditmcview

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. mc_acs_map
  18. help_show
  19. help_help
  20. help_index
  21. help_back
  22. help_next_link
  23. help_prev_link
  24. help_next_node
  25. help_prev_node
  26. help_select_link
  27. help_execute_cmd
  28. help_handle_key
  29. help_bg_callback
  30. help_resize
  31. help_callback
  32. interactive_display_finish
  33. translate_file
  34. md_callback
  35. help_mouse_callback
  36. mousedispatch_new
  37. help_interactive_display

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

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