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         ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
 125 
 126     mcview_display_clean (view);
 127 
 128     /* Find the first displayable changed byte */
 129     /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
 130     from = view->dpy_start;
 131 #ifdef HAVE_CHARSET
 132     if (view->utf8)
 133     {
 134         if (from >= view->bytes_per_line)
 135         {
 136             row--;
 137             from -= view->bytes_per_line;
 138         }
 139         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
 140         {
 141             row--;
 142             from -= view->bytes_per_line;
 143         }
 144     }
 145 #endif /* HAVE_CHARSET */
 146     while (curr && (curr->offset < from))
 147     {
 148         curr = curr->next;
 149     }
 150 
 151     for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
 152     {
 153         int col = 0;
 154         int bytes;              /* Number of bytes already printed on the line */
 155 
 156         /* Print the hex offset */
 157         if (row >= 0)
 158         {
 159             int i;
 160 
 161             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 162             widget_gotoyx (view, r->y + row, r->x);
 163             tty_setcolor (VIEW_BOLD_COLOR);
 164             for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
 165                 tty_print_char (hex_buff[i]);
 166             tty_setcolor (VIEW_NORMAL_COLOR);
 167         }
 168 
 169         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
 170         {
 171             int c;
 172 #ifdef HAVE_CHARSET
 173             int ch = 0;
 174 
 175             if (view->utf8)
 176             {
 177                 struct hexedit_change_node *corr = curr;
 178 
 179                 if (cont_bytes != 0)
 180                 {
 181                     /* UTF-8 continuation bytes, print a space (with proper attributes)... */
 182                     cont_bytes--;
 183                     ch = ' ';
 184                     if (cjk_right)
 185                     {
 186                         /* ... except when it'd wipe out the right half of a CJK, then print nothing */
 187                         cjk_right = FALSE;
 188                         ch = -1;
 189                     }
 190                 }
 191                 else
 192                 {
 193                     int j;
 194                     gchar utf8buf[UTF8_CHAR_LEN + 1];
 195                     int res;
 196                     int first_changed = -1;
 197 
 198                     for (j = 0; j < UTF8_CHAR_LEN; j++)
 199                     {
 200                         if (mcview_get_byte (view, from + j, &res))
 201                             utf8buf[j] = res;
 202                         else
 203                         {
 204                             utf8buf[j] = '\0';
 205                             break;
 206                         }
 207                         if (curr != NULL && from + j == curr->offset)
 208                         {
 209                             utf8buf[j] = curr->value;
 210                             if (first_changed == -1)
 211                                 first_changed = j;
 212                         }
 213                         if (curr != NULL && from + j >= curr->offset)
 214                             curr = curr->next;
 215                     }
 216                     utf8buf[UTF8_CHAR_LEN] = '\0';
 217 
 218                     /* Determine the state of the current multibyte char */
 219                     ch = g_utf8_get_char_validated (utf8buf, -1);
 220                     if (ch == -1 || ch == -2)
 221                     {
 222                         ch = '.';
 223                     }
 224                     else
 225                     {
 226                         gchar *next_ch;
 227 
 228                         next_ch = g_utf8_next_char (utf8buf);
 229                         cont_bytes = next_ch - utf8buf - 1;
 230                         if (g_unichar_iswide (ch))
 231                             cjk_right = TRUE;
 232                     }
 233 
 234                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
 235                     curr = corr;
 236                 }
 237             }
 238 #endif /* HAVE_CHARSET */
 239 
 240             /* For negative rows, the only thing we care about is overflowing
 241              * UTF-8 continuation bytes which were handled above. */
 242             if (row < 0)
 243             {
 244                 if (curr != NULL && from == curr->offset)
 245                     curr = curr->next;
 246                 continue;
 247             }
 248 
 249             if (!mcview_get_byte (view, from, &c))
 250                 break;
 251 
 252             /* Save the cursor position for mcview_place_cursor() */
 253             if (from == view->hex_cursor && !view->hexview_in_text)
 254             {
 255                 view->cursor_row = row;
 256                 view->cursor_col = col;
 257             }
 258 
 259             /* Determine the state of the current byte */
 260             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
 261             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
 262 
 263             /* Determine the value of the current byte */
 264             if (curr != NULL && from == curr->offset)
 265             {
 266                 c = curr->value;
 267                 curr = curr->next;
 268             }
 269 
 270             /* Select the color for the hex number */
 271             tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 272                           boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
 273                           boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 274                           /* boldflag_byte == MARK_CURSOR */
 275                           view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
 276 
 277             /* Print the hex number */
 278             widget_gotoyx (view, r->y + row, r->x + col);
 279             if (col < r->cols)
 280             {
 281                 tty_print_char (hex_char[c / 16]);
 282                 col += 1;
 283             }
 284             if (col < r->cols)
 285             {
 286                 tty_print_char (hex_char[c % 16]);
 287                 col += 1;
 288             }
 289 
 290             /* Print the separator */
 291             tty_setcolor (VIEW_NORMAL_COLOR);
 292             if (bytes != view->bytes_per_line - 1)
 293             {
 294                 if (col < r->cols)
 295                 {
 296                     tty_print_char (' ');
 297                     col += 1;
 298                 }
 299 
 300                 /* After every four bytes, print a group separator */
 301                 if (bytes % 4 == 3)
 302                 {
 303                     if (view->data_area.cols >= 80 && col < r->cols)
 304                     {
 305                         tty_print_one_vline (TRUE);
 306                         col += 1;
 307                     }
 308                     if (col < r->cols)
 309                     {
 310                         tty_print_char (' ');
 311                         col += 1;
 312                     }
 313                 }
 314             }
 315 
 316             /* Select the color for the character; this differs from the
 317              * hex color when boldflag == MARK_CURSOR */
 318             tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 319                           boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
 320                           boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 321                           /* boldflag_char == MARK_CURSOR */
 322                           view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
 323 
 324 
 325 #ifdef HAVE_CHARSET
 326             if (mc_global.utf8_display)
 327             {
 328                 if (!view->utf8)
 329                 {
 330                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 331                 }
 332                 if (!g_unichar_isprint (c))
 333                     c = '.';
 334             }
 335             else if (view->utf8)
 336                 ch = convert_from_utf_to_current_c (ch, view->converter);
 337             else
 338 #endif
 339             {
 340 #ifdef HAVE_CHARSET
 341                 c = convert_to_display_c (c);
 342 #endif
 343 
 344                 if (!is_printable (c))
 345                     c = '.';
 346             }
 347 
 348             /* Print corresponding character on the text side */
 349             if (text_start + bytes < r->cols)
 350             {
 351                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 352 #ifdef HAVE_CHARSET
 353                 if (view->utf8)
 354                     tty_print_anychar (ch);
 355                 else
 356 #endif
 357                     tty_print_char (c);
 358             }
 359 
 360             /* Save the cursor position for mcview_place_cursor() */
 361             if (from == view->hex_cursor && view->hexview_in_text)
 362             {
 363                 view->cursor_row = row;
 364                 view->cursor_col = text_start + bytes;
 365             }
 366         }
 367     }
 368 
 369     /* Be polite to the other functions */
 370     tty_setcolor (VIEW_NORMAL_COLOR);
 371 
 372     mcview_place_cursor (view);
 373     view->dpy_end = from;
 374 }
 375 
 376 /* --------------------------------------------------------------------------------------------- */
 377 
 378 gboolean
 379 mcview_hexedit_save_changes (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 380 {
 381     int answer = 0;
 382 
 383     if (view->change_list == NULL)
 384         return TRUE;
 385 
 386     while (answer == 0)
 387     {
 388         int fp;
 389         char *text;
 390         struct hexedit_change_node *curr, *next;
 391 
 392         g_assert (view->filename_vpath != NULL);
 393 
 394         fp = mc_open (view->filename_vpath, O_WRONLY);
 395         if (fp != -1)
 396         {
 397             for (curr = view->change_list; curr != NULL; curr = next)
 398             {
 399                 next = curr->next;
 400 
 401                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 402                     || mc_write (fp, &(curr->value), 1) != 1)
 403                     goto save_error;
 404 
 405                 /* delete the saved item from the change list */
 406                 view->change_list = next;
 407                 view->dirty++;
 408                 mcview_set_byte (view, curr->offset, curr->value);
 409                 g_free (curr);
 410             }
 411 
 412             view->change_list = NULL;
 413 
 414             if (view->locked)
 415                 view->locked = unlock_file (view->filename_vpath);
 416 
 417             if (mc_close (fp) == -1)
 418                 message (D_ERROR, _("Save file"),
 419                          _("Error while closing the file:\n%s\n"
 420                            "Data may have been written or not"), unix_error_string (errno));
 421 
 422             view->dirty++;
 423             return TRUE;
 424         }
 425 
 426       save_error:
 427         text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
 428         (void) mc_close (fp);
 429 
 430         answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
 431         g_free (text);
 432     }
 433 
 434     return FALSE;
 435 }
 436 
 437 /* --------------------------------------------------------------------------------------------- */
 438 
 439 void
 440 mcview_toggle_hexedit_mode (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 441 {
 442     view->hexedit_mode = !view->hexedit_mode;
 443     view->dpy_bbar_dirty = TRUE;
 444     view->dirty++;
 445 }
 446 
 447 /* --------------------------------------------------------------------------------------------- */
 448 
 449 void
 450 mcview_hexedit_free_change_list (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     struct hexedit_change_node *curr, *next;
 453 
 454     for (curr = view->change_list; curr != NULL; curr = next)
 455     {
 456         next = curr->next;
 457         g_free (curr);
 458     }
 459     view->change_list = NULL;
 460 
 461     if (view->locked)
 462         view->locked = unlock_file (view->filename_vpath);
 463 
 464     view->dirty++;
 465 }
 466 
 467 /* --------------------------------------------------------------------------------------------- */
 468 
 469 void
 470 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 471 {
 472     /* chnode always either points to the head of the list or
 473      * to one of the ->next fields in the list. The value at
 474      * this location will be overwritten with the new node.   */
 475     struct hexedit_change_node **chnode = head;
 476 
 477     while (*chnode != NULL && (*chnode)->offset < node->offset)
 478         chnode = &((*chnode)->next);
 479 
 480     node->next = *chnode;
 481     *chnode = node;
 482 }
 483 
 484 /* --------------------------------------------------------------------------------------------- */

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