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

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