Manual pages: mcmcdiffmceditmcview

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-2025
   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 <https://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 #include "lib/charsets.h"
  49 
  50 #include "internal.h"
  51 
  52 /*** global variables ****************************************************************************/
  53 
  54 /*** file scope macro definitions ****************************************************************/
  55 
  56 /*** file scope type declarations ****************************************************************/
  57 
  58 typedef enum
  59 {
  60     MARK_NORMAL,
  61     MARK_SELECTED,
  62     MARK_CURSOR,
  63     MARK_CHANGED
  64 } mark_t;
  65 
  66 /*** forward declarations (file scope functions) *************************************************/
  67 
  68 /*** file scope variables ************************************************************************/
  69 
  70 static const char hex_char[] = "0123456789ABCDEF";
  71 
  72 /* --------------------------------------------------------------------------------------------- */
  73 /*** file scope functions ************************************************************************/
  74 /* --------------------------------------------------------------------------------------------- */
  75 /** Determine the state of the current byte.
  76  *
  77  * @param view viewer object
  78  * @param from offset
  79  * @param curr current node
  80  */
  81 
  82 static mark_t
  83 mcview_hex_calculate_boldflag (WView *view, off_t from, struct hexedit_change_node *curr,
     /* [previous][next][first][last][top][bottom][index][help]  */
  84                                gboolean force_changed)
  85 {
  86     return (from == view->hex_cursor)                               ? MARK_CURSOR
  87         : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
  88         : (view->search_start <= from && from < view->search_end)   ? MARK_SELECTED
  89                                                                     : MARK_NORMAL;
  90 }
  91 
  92 /* --------------------------------------------------------------------------------------------- */
  93 /*** public functions ****************************************************************************/
  94 /* --------------------------------------------------------------------------------------------- */
  95 
  96 void
  97 mcview_display_hex (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
  98 {
  99     const WRect *r = &view->data_area;
 100     int ngroups = view->bytes_per_line / 4;
 101     /* 8 characters are used for the file offset, and every hex group
 102      * takes 13 characters. Starting at width of 80 columns, the groups
 103      * are separated by an extra vertical line. Starting at width of 81,
 104      * there is an extra space before the text column. There is always a
 105      * mostly empty column on the right, to allow overflowing CJKs.
 106      */
 107     int text_start;
 108 
 109     int row = 0;
 110     off_t from;
 111     mark_t boldflag_byte = MARK_NORMAL;
 112     mark_t boldflag_char = MARK_NORMAL;
 113     struct hexedit_change_node *curr = view->change_list;
 114     int cont_bytes = 0;             // number of continuation bytes remanining from current UTF-8
 115     gboolean cjk_right = FALSE;     // whether the second byte of a CJK is to be processed
 116     gboolean utf8_changed = FALSE;  // whether any of the bytes in the UTF-8 were changed
 117 
 118     char hex_buff[10];  // A temporary buffer for sprintf and mvwaddstr
 119 
 120     text_start = 8 + 13 * ngroups;
 121     if (r->cols == 80)
 122         text_start += ngroups - 1;
 123     else if (r->cols > 80)
 124         text_start += ngroups;
 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     if (view->utf8)
 132     {
 133         if (from >= view->bytes_per_line)
 134         {
 135             row--;
 136             from -= view->bytes_per_line;
 137         }
 138         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
 139         {
 140             row--;
 141             from -= view->bytes_per_line;
 142         }
 143     }
 144 
 145     while (curr != NULL && (curr->offset < from))
 146         curr = curr->next;
 147 
 148     for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
 149     {
 150         int col = 0;
 151         int bytes;  // Number of bytes already printed on the line
 152 
 153         // Print the hex offset
 154         if (row >= 0)
 155         {
 156             int i;
 157 
 158             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 159             widget_gotoyx (view, r->y + row, r->x);
 160             tty_setcolor (VIEWER_BOLD_COLOR);
 161             for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
 162                 tty_print_char (hex_buff[i]);
 163             tty_setcolor (VIEWER_NORMAL_COLOR);
 164         }
 165 
 166         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
 167         {
 168             int c;
 169             int ch = 0;
 170 
 171             if (view->utf8)
 172             {
 173                 struct hexedit_change_node *corr = curr;
 174 
 175                 if (cont_bytes != 0)
 176                 {
 177                     // UTF-8 continuation bytes, print a space (with proper attributes)...
 178                     cont_bytes--;
 179                     ch = ' ';
 180                     if (cjk_right)
 181                     {
 182                         // ... except when it'd wipe out the right half of a CJK, then print nothing
 183                         cjk_right = FALSE;
 184                         ch = -1;
 185                     }
 186                 }
 187                 else
 188                 {
 189                     int j;
 190                     gchar utf8buf[MB_LEN_MAX + 1];
 191                     int res;
 192                     int first_changed = -1;
 193 
 194                     for (j = 0; j < MB_LEN_MAX; j++)
 195                     {
 196                         if (mcview_get_byte (view, from + j, &res))
 197                             utf8buf[j] = res;
 198                         else
 199                         {
 200                             utf8buf[j] = '\0';
 201                             break;
 202                         }
 203                         if (curr != NULL && from + j == curr->offset)
 204                         {
 205                             utf8buf[j] = curr->value;
 206                             if (first_changed == -1)
 207                                 first_changed = j;
 208                         }
 209                         if (curr != NULL && from + j >= curr->offset)
 210                             curr = curr->next;
 211                     }
 212                     utf8buf[MB_LEN_MAX] = '\0';
 213 
 214                     // Determine the state of the current multibyte char
 215                     ch = g_utf8_get_char_validated (utf8buf, -1);
 216                     if (ch == -1 || ch == -2)
 217                         ch = '.';
 218                     else
 219                     {
 220                         gchar *next_ch;
 221 
 222                         next_ch = g_utf8_next_char (utf8buf);
 223                         cont_bytes = next_ch - utf8buf - 1;
 224                         if (g_unichar_iswide (ch))
 225                             cjk_right = TRUE;
 226                     }
 227 
 228                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
 229                     curr = corr;
 230                 }
 231             }
 232 
 233             /* For negative rows, the only thing we care about is overflowing
 234              * UTF-8 continuation bytes which were handled above. */
 235             if (row < 0)
 236             {
 237                 if (curr != NULL && from == curr->offset)
 238                     curr = curr->next;
 239                 continue;
 240             }
 241 
 242             if (!mcview_get_byte (view, from, &c))
 243                 break;
 244 
 245             // Save the cursor position for mcview_place_cursor()
 246             if (from == view->hex_cursor && !view->hexview_in_text)
 247             {
 248                 view->cursor_row = row;
 249                 view->cursor_col = col;
 250             }
 251 
 252             // Determine the state of the current byte
 253             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
 254             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
 255 
 256             // Determine the value of the current byte
 257             if (curr != NULL && from == curr->offset)
 258             {
 259                 c = curr->value;
 260                 curr = curr->next;
 261             }
 262 
 263             // Select the color for the hex number
 264             tty_setcolor (boldflag_byte == MARK_NORMAL         ? VIEWER_NORMAL_COLOR
 265                               : boldflag_byte == MARK_SELECTED ? VIEWER_BOLD_COLOR
 266                               : boldflag_byte == MARK_CHANGED  ? VIEWER_UNDERLINED_COLOR
 267                                                                :
 268                                                               // boldflag_byte == MARK_CURSOR
 269                               view->hexview_in_text ? VIEWER_SELECTED_COLOR
 270                                                     : VIEWER_UNDERLINED_COLOR);
 271 
 272             // Print the hex number
 273             widget_gotoyx (view, r->y + row, r->x + col);
 274             if (col < r->cols)
 275             {
 276                 tty_print_char (hex_char[c / 16]);
 277                 col++;
 278             }
 279             if (col < r->cols)
 280             {
 281                 tty_print_char (hex_char[c % 16]);
 282                 col++;
 283             }
 284 
 285             // Print the separator
 286             tty_setcolor (VIEWER_NORMAL_COLOR);
 287             if (bytes != view->bytes_per_line - 1)
 288             {
 289                 if (col < r->cols)
 290                 {
 291                     tty_print_char (' ');
 292                     col++;
 293                 }
 294 
 295                 // After every four bytes, print a group separator
 296                 if (bytes % 4 == 3)
 297                 {
 298                     if (view->data_area.cols >= 80 && col < r->cols)
 299                     {
 300                         tty_setcolor (VIEWER_FRAME_COLOR);
 301                         tty_print_one_vline (TRUE);
 302                         tty_setcolor (VIEWER_NORMAL_COLOR);
 303                         col++;
 304                     }
 305                     if (col < r->cols)
 306                     {
 307                         tty_print_char (' ');
 308                         col++;
 309                     }
 310                 }
 311             }
 312 
 313             /* Select the color for the character; this differs from the
 314              * hex color when boldflag == MARK_CURSOR */
 315             tty_setcolor (boldflag_char == MARK_NORMAL         ? VIEWER_NORMAL_COLOR
 316                               : boldflag_char == MARK_SELECTED ? VIEWER_BOLD_COLOR
 317                               : boldflag_char == MARK_CHANGED  ? VIEWER_UNDERLINED_COLOR
 318                                                                :
 319                                                               // boldflag_char == MARK_CURSOR
 320                               view->hexview_in_text ? VIEWER_SELECTED_COLOR
 321                                                     : CORE_MARKED_SELECTED_COLOR);
 322 
 323             if (mc_global.utf8_display)
 324             {
 325                 if (!view->utf8)
 326                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 327                 if (!g_unichar_isprint (c))
 328                     c = '.';
 329             }
 330             else if (view->utf8)
 331                 ch = convert_from_utf_to_current_c (ch, view->converter);
 332             else
 333             {
 334                 c = convert_to_display_c (c);
 335 
 336                 if (!is_printable (c))
 337                     c = '.';
 338             }
 339 
 340             // Print corresponding character on the text side
 341             if (text_start + bytes < r->cols)
 342             {
 343                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 344                 if (view->utf8)
 345                     tty_print_anychar (ch);
 346                 else
 347                     tty_print_char (c);
 348             }
 349 
 350             // Save the cursor position for mcview_place_cursor()
 351             if (from == view->hex_cursor && view->hexview_in_text)
 352             {
 353                 view->cursor_row = row;
 354                 view->cursor_col = text_start + bytes;
 355             }
 356         }
 357     }
 358 
 359     // Be polite to the other functions
 360     tty_setcolor (VIEWER_NORMAL_COLOR);
 361 
 362     mcview_place_cursor (view);
 363     view->dpy_end = from;
 364 }
 365 
 366 /* --------------------------------------------------------------------------------------------- */
 367 
 368 gboolean
 369 mcview_hexedit_save_changes (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 370 {
 371     int answer = 0;
 372 
 373     if (view->change_list == NULL)
 374         return TRUE;
 375 
 376     while (answer == 0)
 377     {
 378         int fp;
 379         char *text;
 380         struct hexedit_change_node *curr, *next;
 381 
 382         g_assert (view->filename_vpath != NULL);
 383 
 384         fp = mc_open (view->filename_vpath, O_WRONLY);
 385         if (fp != -1)
 386         {
 387             for (curr = view->change_list; curr != NULL; curr = next)
 388             {
 389                 next = curr->next;
 390 
 391                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 392                     || mc_write (fp, &(curr->value), 1) != 1)
 393                     goto save_error;
 394 
 395                 // delete the saved item from the change list
 396                 view->change_list = next;
 397                 view->dirty++;
 398                 mcview_set_byte (view, curr->offset, curr->value);
 399                 g_free (curr);
 400             }
 401 
 402             view->change_list = NULL;
 403 
 404             if (view->locked)
 405                 view->locked = unlock_file (view->filename_vpath) != 0;
 406 
 407             if (mc_close (fp) == -1)
 408                 message (D_ERROR, _ ("Save file"),
 409                          _ ("Error while closing the file:\n%s\n"
 410                             "Data may have been written or not"),
 411                          unix_error_string (errno));
 412 
 413             view->dirty++;
 414             return TRUE;
 415         }
 416 
 417     save_error:
 418         text = g_strdup_printf (_ ("Cannot save file:\n%s"), unix_error_string (errno));
 419         (void) mc_close (fp);
 420 
 421         answer = query_dialog (_ ("Save file"), text, D_ERROR, 2, _ ("&Retry"), _ ("&Cancel"));
 422         g_free (text);
 423     }
 424 
 425     return FALSE;
 426 }
 427 
 428 /* --------------------------------------------------------------------------------------------- */
 429 
 430 void
 431 mcview_toggle_hexedit_mode (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 432 {
 433     view->hexedit_mode = !view->hexedit_mode;
 434     view->dpy_bbar_dirty = TRUE;
 435     view->dirty++;
 436 }
 437 
 438 /* --------------------------------------------------------------------------------------------- */
 439 
 440 void
 441 mcview_hexedit_free_change_list (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 442 {
 443     struct hexedit_change_node *curr, *next;
 444 
 445     for (curr = view->change_list; curr != NULL; curr = next)
 446     {
 447         next = curr->next;
 448         g_free (curr);
 449     }
 450     view->change_list = NULL;
 451 
 452     if (view->locked)
 453         view->locked = unlock_file (view->filename_vpath) != 0;
 454 
 455     view->dirty++;
 456 }
 457 
 458 /* --------------------------------------------------------------------------------------------- */
 459 
 460 void
 461 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 462 {
 463     /* chnode always either points to the head of the list or
 464      * to one of the ->next fields in the list. The value at
 465      * this location will be overwritten with the new node.   */
 466     struct hexedit_change_node **chnode = head;
 467 
 468     while (*chnode != NULL && (*chnode)->offset < node->offset)
 469         chnode = &((*chnode)->next);
 470 
 471     node->next = *chnode;
 472     *chnode = node;
 473 }
 474 
 475 /* --------------------------------------------------------------------------------------------- */

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