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

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