root/src/viewer/hex.c

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

DEFINITIONS

This source file includes following definitions.
  1. mcview_hex_calculate_boldflag
  2. mcview_display_hex
  3. mcview_hexedit_save_changes
  4. mcview_toggle_hexedit_mode
  5. mcview_hexedit_free_change_list
  6. mcview_enqueue_change

   1 /*
   2    Internal file viewer for the Midnight Commander
   3    Function for hex view
   4 
   5    Copyright (C) 1994-2024
   6    Free Software Foundation, Inc.
   7 
   8    Written by:
   9    Miguel de Icaza, 1994, 1995, 1998
  10    Janne Kukonlehto, 1994, 1995
  11    Jakub Jelinek, 1995
  12    Joseph M. Hinkle, 1996
  13    Norbert Warmuth, 1997
  14    Pavel Machek, 1998
  15    Roland Illig <roland.illig@gmx.de>, 2004, 2005
  16    Slava Zanko <slavazanko@google.com>, 2009, 2013
  17    Andrew Borodin <aborodin@vmail.ru>, 2009-2022
  18    Ilia Maslakov <il.smind@gmail.com>, 2009
  19 
  20    This file is part of the Midnight Commander.
  21 
  22    The Midnight Commander is free software: you can redistribute it
  23    and/or modify it under the terms of the GNU General Public License as
  24    published by the Free Software Foundation, either version 3 of the License,
  25    or (at your option) any later version.
  26 
  27    The Midnight Commander is distributed in the hope that it will be useful,
  28    but WITHOUT ANY WARRANTY; without even the implied warranty of
  29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  30    GNU General Public License for more details.
  31 
  32    You should have received a copy of the GNU General Public License
  33    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  34  */
  35 
  36 #include <config.h>
  37 
  38 #include <errno.h>
  39 #include <inttypes.h>           /* uintmax_t */
  40 
  41 #include "lib/global.h"
  42 #include "lib/tty/tty.h"
  43 #include "lib/skin.h"
  44 #include "lib/vfs/vfs.h"
  45 #include "lib/lock.h"           /* lock_file() and unlock_file() */
  46 #include "lib/util.h"
  47 #include "lib/widget.h"
  48 #ifdef HAVE_CHARSET
  49 #include "lib/charsets.h"
  50 #endif
  51 
  52 #include "internal.h"
  53 
  54 /*** global variables ****************************************************************************/
  55 
  56 /*** file scope macro definitions ****************************************************************/
  57 
  58 /*** file scope type declarations ****************************************************************/
  59 
  60 typedef enum
  61 {
  62     MARK_NORMAL,
  63     MARK_SELECTED,
  64     MARK_CURSOR,
  65     MARK_CHANGED
  66 } mark_t;
  67 
  68 /*** forward declarations (file scope functions) *************************************************/
  69 
  70 /*** file scope variables ************************************************************************/
  71 
  72 static const char hex_char[] = "0123456789ABCDEF";
  73 
  74 /* --------------------------------------------------------------------------------------------- */
  75 /*** file scope functions ************************************************************************/
  76 /* --------------------------------------------------------------------------------------------- */
  77 /** Determine the state of the current byte.
  78  *
  79  * @param view viewer object
  80  * @param from offset
  81  * @param curr current node
  82  */
  83 
  84 static mark_t
  85 mcview_hex_calculate_boldflag (WView *view, off_t from, struct hexedit_change_node *curr,
     /* [previous][next][first][last][top][bottom][index][help]  */
  86                                gboolean force_changed)
  87 {
  88     return (from == view->hex_cursor) ? MARK_CURSOR
  89         : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
  90         : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
  91 }
  92 
  93 /* --------------------------------------------------------------------------------------------- */
  94 /*** public functions ****************************************************************************/
  95 /* --------------------------------------------------------------------------------------------- */
  96 
  97 void
  98 mcview_display_hex (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
  99 {
 100     const WRect *r = &view->data_area;
 101     int ngroups = view->bytes_per_line / 4;
 102     /* 8 characters are used for the file offset, and every hex group
 103      * takes 13 characters. Starting at width of 80 columns, the groups
 104      * are separated by an extra vertical line. Starting at width of 81,
 105      * there is an extra space before the text column. There is always a
 106      * mostly empty column on the right, to allow overflowing CJKs.
 107      */
 108     int text_start;
 109 
 110     int row = 0;
 111     off_t from;
 112     mark_t boldflag_byte = MARK_NORMAL;
 113     mark_t boldflag_char = MARK_NORMAL;
 114     struct hexedit_change_node *curr = view->change_list;
 115 #ifdef HAVE_CHARSET
 116     int cont_bytes = 0;         /* number of continuation bytes remanining from current UTF-8 */
 117     gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
 118 #endif /* HAVE_CHARSET */
 119     gboolean utf8_changed = FALSE;      /* whether any of the bytes in the UTF-8 were changed */
 120 
 121     char hex_buff[10];          /* A temporary buffer for sprintf and mvwaddstr */
 122 
 123     text_start = 8 + 13 * ngroups;
 124     if (r->cols == 80)
 125         text_start += ngroups - 1;
 126     else if (r->cols > 80)
 127         text_start += ngroups;
 128 
 129     mcview_display_clean (view);
 130 
 131     /* Find the first displayable changed byte */
 132     /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
 133     from = view->dpy_start;
 134 #ifdef HAVE_CHARSET
 135     if (view->utf8)
 136     {
 137         if (from >= view->bytes_per_line)
 138         {
 139             row--;
 140             from -= view->bytes_per_line;
 141         }
 142         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
 143         {
 144             row--;
 145             from -= view->bytes_per_line;
 146         }
 147     }
 148 #endif /* HAVE_CHARSET */
 149     while (curr != NULL && (curr->offset < from))
 150         curr = curr->next;
 151 
 152     for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
 153     {
 154         int col = 0;
 155         int bytes;              /* Number of bytes already printed on the line */
 156 
 157         /* Print the hex offset */
 158         if (row >= 0)
 159         {
 160             int i;
 161 
 162             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 163             widget_gotoyx (view, r->y + row, r->x);
 164             tty_setcolor (VIEW_BOLD_COLOR);
 165             for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
 166                 tty_print_char (hex_buff[i]);
 167             tty_setcolor (VIEW_NORMAL_COLOR);
 168         }
 169 
 170         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
 171         {
 172             int c;
 173 #ifdef HAVE_CHARSET
 174             int ch = 0;
 175 
 176             if (view->utf8)
 177             {
 178                 struct hexedit_change_node *corr = curr;
 179 
 180                 if (cont_bytes != 0)
 181                 {
 182                     /* UTF-8 continuation bytes, print a space (with proper attributes)... */
 183                     cont_bytes--;
 184                     ch = ' ';
 185                     if (cjk_right)
 186                     {
 187                         /* ... except when it'd wipe out the right half of a CJK, then print nothing */
 188                         cjk_right = FALSE;
 189                         ch = -1;
 190                     }
 191                 }
 192                 else
 193                 {
 194                     int j;
 195                     gchar utf8buf[UTF8_CHAR_LEN + 1];
 196                     int res;
 197                     int first_changed = -1;
 198 
 199                     for (j = 0; j < UTF8_CHAR_LEN; j++)
 200                     {
 201                         if (mcview_get_byte (view, from + j, &res))
 202                             utf8buf[j] = res;
 203                         else
 204                         {
 205                             utf8buf[j] = '\0';
 206                             break;
 207                         }
 208                         if (curr != NULL && from + j == curr->offset)
 209                         {
 210                             utf8buf[j] = curr->value;
 211                             if (first_changed == -1)
 212                                 first_changed = j;
 213                         }
 214                         if (curr != NULL && from + j >= curr->offset)
 215                             curr = curr->next;
 216                     }
 217                     utf8buf[UTF8_CHAR_LEN] = '\0';
 218 
 219                     /* Determine the state of the current multibyte char */
 220                     ch = g_utf8_get_char_validated (utf8buf, -1);
 221                     if (ch == -1 || ch == -2)
 222                         ch = '.';
 223                     else
 224                     {
 225                         gchar *next_ch;
 226 
 227                         next_ch = g_utf8_next_char (utf8buf);
 228                         cont_bytes = next_ch - utf8buf - 1;
 229                         if (g_unichar_iswide (ch))
 230                             cjk_right = TRUE;
 231                     }
 232 
 233                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
 234                     curr = corr;
 235                 }
 236             }
 237 #endif /* HAVE_CHARSET */
 238 
 239             /* For negative rows, the only thing we care about is overflowing
 240              * UTF-8 continuation bytes which were handled above. */
 241             if (row < 0)
 242             {
 243                 if (curr != NULL && from == curr->offset)
 244                     curr = curr->next;
 245                 continue;
 246             }
 247 
 248             if (!mcview_get_byte (view, from, &c))
 249                 break;
 250 
 251             /* Save the cursor position for mcview_place_cursor() */
 252             if (from == view->hex_cursor && !view->hexview_in_text)
 253             {
 254                 view->cursor_row = row;
 255                 view->cursor_col = col;
 256             }
 257 
 258             /* Determine the state of the current byte */
 259             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
 260             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
 261 
 262             /* Determine the value of the current byte */
 263             if (curr != NULL && from == curr->offset)
 264             {
 265                 c = curr->value;
 266                 curr = curr->next;
 267             }
 268 
 269             /* Select the color for the hex number */
 270             tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 271                           boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
 272                           boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 273                           /* boldflag_byte == MARK_CURSOR */
 274                           view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
 275 
 276             /* Print the hex number */
 277             widget_gotoyx (view, r->y + row, r->x + col);
 278             if (col < r->cols)
 279             {
 280                 tty_print_char (hex_char[c / 16]);
 281                 col++;
 282             }
 283             if (col < r->cols)
 284             {
 285                 tty_print_char (hex_char[c % 16]);
 286                 col++;
 287             }
 288 
 289             /* Print the separator */
 290             tty_setcolor (VIEW_NORMAL_COLOR);
 291             if (bytes != view->bytes_per_line - 1)
 292             {
 293                 if (col < r->cols)
 294                 {
 295                     tty_print_char (' ');
 296                     col++;
 297                 }
 298 
 299                 /* After every four bytes, print a group separator */
 300                 if (bytes % 4 == 3)
 301                 {
 302                     if (view->data_area.cols >= 80 && col < r->cols)
 303                     {
 304                         tty_print_one_vline (TRUE);
 305                         col++;
 306                     }
 307                     if (col < r->cols)
 308                     {
 309                         tty_print_char (' ');
 310                         col++;
 311                     }
 312                 }
 313             }
 314 
 315             /* Select the color for the character; this differs from the
 316              * hex color when boldflag == MARK_CURSOR */
 317             tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 318                           boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
 319                           boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 320                           /* boldflag_char == MARK_CURSOR */
 321                           view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
 322 
 323 
 324 #ifdef HAVE_CHARSET
 325             if (mc_global.utf8_display)
 326             {
 327                 if (!view->utf8)
 328                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 329                 if (!g_unichar_isprint (c))
 330                     c = '.';
 331             }
 332             else if (view->utf8)
 333                 ch = convert_from_utf_to_current_c (ch, view->converter);
 334             else
 335 #endif
 336             {
 337 #ifdef HAVE_CHARSET
 338                 c = convert_to_display_c (c);
 339 #endif
 340 
 341                 if (!is_printable (c))
 342                     c = '.';
 343             }
 344 
 345             /* Print corresponding character on the text side */
 346             if (text_start + bytes < r->cols)
 347             {
 348                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 349 #ifdef HAVE_CHARSET
 350                 if (view->utf8)
 351                     tty_print_anychar (ch);
 352                 else
 353 #endif
 354                     tty_print_char (c);
 355             }
 356 
 357             /* Save the cursor position for mcview_place_cursor() */
 358             if (from == view->hex_cursor && view->hexview_in_text)
 359             {
 360                 view->cursor_row = row;
 361                 view->cursor_col = text_start + bytes;
 362             }
 363         }
 364     }
 365 
 366     /* Be polite to the other functions */
 367     tty_setcolor (VIEW_NORMAL_COLOR);
 368 
 369     mcview_place_cursor (view);
 370     view->dpy_end = from;
 371 }
 372 
 373 /* --------------------------------------------------------------------------------------------- */
 374 
 375 gboolean
 376 mcview_hexedit_save_changes (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 377 {
 378     int answer = 0;
 379 
 380     if (view->change_list == NULL)
 381         return TRUE;
 382 
 383     while (answer == 0)
 384     {
 385         int fp;
 386         char *text;
 387         struct hexedit_change_node *curr, *next;
 388 
 389         g_assert (view->filename_vpath != NULL);
 390 
 391         fp = mc_open (view->filename_vpath, O_WRONLY);
 392         if (fp != -1)
 393         {
 394             for (curr = view->change_list; curr != NULL; curr = next)
 395             {
 396                 next = curr->next;
 397 
 398                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 399                     || mc_write (fp, &(curr->value), 1) != 1)
 400                     goto save_error;
 401 
 402                 /* delete the saved item from the change list */
 403                 view->change_list = next;
 404                 view->dirty++;
 405                 mcview_set_byte (view, curr->offset, curr->value);
 406                 g_free (curr);
 407             }
 408 
 409             view->change_list = NULL;
 410 
 411             if (view->locked)
 412                 view->locked = unlock_file (view->filename_vpath);
 413 
 414             if (mc_close (fp) == -1)
 415                 message (D_ERROR, _("Save file"),
 416                          _("Error while closing the file:\n%s\n"
 417                            "Data may have been written or not"), unix_error_string (errno));
 418 
 419             view->dirty++;
 420             return TRUE;
 421         }
 422 
 423       save_error:
 424         text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
 425         (void) mc_close (fp);
 426 
 427         answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
 428         g_free (text);
 429     }
 430 
 431     return FALSE;
 432 }
 433 
 434 /* --------------------------------------------------------------------------------------------- */
 435 
 436 void
 437 mcview_toggle_hexedit_mode (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 438 {
 439     view->hexedit_mode = !view->hexedit_mode;
 440     view->dpy_bbar_dirty = TRUE;
 441     view->dirty++;
 442 }
 443 
 444 /* --------------------------------------------------------------------------------------------- */
 445 
 446 void
 447 mcview_hexedit_free_change_list (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 448 {
 449     struct hexedit_change_node *curr, *next;
 450 
 451     for (curr = view->change_list; curr != NULL; curr = next)
 452     {
 453         next = curr->next;
 454         g_free (curr);
 455     }
 456     view->change_list = NULL;
 457 
 458     if (view->locked)
 459         view->locked = unlock_file (view->filename_vpath);
 460 
 461     view->dirty++;
 462 }
 463 
 464 /* --------------------------------------------------------------------------------------------- */
 465 
 466 void
 467 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 468 {
 469     /* chnode always either points to the head of the list or
 470      * to one of the ->next fields in the list. The value at
 471      * this location will be overwritten with the new node.   */
 472     struct hexedit_change_node **chnode = head;
 473 
 474     while (*chnode != NULL && (*chnode)->offset < node->offset)
 475         chnode = &((*chnode)->next);
 476 
 477     node->next = *chnode;
 478     *chnode = node;
 479 }
 480 
 481 /* --------------------------------------------------------------------------------------------- */

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