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 (VIEW_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 (VIEW_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[UTF8_CHAR_LEN + 1];
 191                     int res;
 192                     int first_changed = -1;
 193 
 194                     for (j = 0; j < UTF8_CHAR_LEN; 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[UTF8_CHAR_LEN] = '\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         ? VIEW_NORMAL_COLOR
 265                               : boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR
 266                               : boldflag_byte == MARK_CHANGED  ? VIEW_UNDERLINED_COLOR
 267                                                                :
 268                                                               // boldflag_byte == MARK_CURSOR
 269                               view->hexview_in_text ? VIEW_SELECTED_COLOR
 270                                                     : VIEW_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 (VIEW_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_print_one_vline (TRUE);
 301                         col++;
 302                     }
 303                     if (col < r->cols)
 304                     {
 305                         tty_print_char (' ');
 306                         col++;
 307                     }
 308                 }
 309             }
 310 
 311             /* Select the color for the character; this differs from the
 312              * hex color when boldflag == MARK_CURSOR */
 313             tty_setcolor (boldflag_char == MARK_NORMAL         ? VIEW_NORMAL_COLOR
 314                               : boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR
 315                               : boldflag_char == MARK_CHANGED  ? VIEW_UNDERLINED_COLOR
 316                                                                :
 317                                                               // boldflag_char == MARK_CURSOR
 318                               view->hexview_in_text ? VIEW_SELECTED_COLOR
 319                                                     : MARKED_SELECTED_COLOR);
 320 
 321             if (mc_global.utf8_display)
 322             {
 323                 if (!view->utf8)
 324                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 325                 if (!g_unichar_isprint (c))
 326                     c = '.';
 327             }
 328             else if (view->utf8)
 329                 ch = convert_from_utf_to_current_c (ch, view->converter);
 330             else
 331             {
 332                 c = convert_to_display_c (c);
 333 
 334                 if (!is_printable (c))
 335                     c = '.';
 336             }
 337 
 338             // Print corresponding character on the text side
 339             if (text_start + bytes < r->cols)
 340             {
 341                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 342                 if (view->utf8)
 343                     tty_print_anychar (ch);
 344                 else
 345                     tty_print_char (c);
 346             }
 347 
 348             // Save the cursor position for mcview_place_cursor()
 349             if (from == view->hex_cursor && view->hexview_in_text)
 350             {
 351                 view->cursor_row = row;
 352                 view->cursor_col = text_start + bytes;
 353             }
 354         }
 355     }
 356 
 357     // Be polite to the other functions
 358     tty_setcolor (VIEW_NORMAL_COLOR);
 359 
 360     mcview_place_cursor (view);
 361     view->dpy_end = from;
 362 }
 363 
 364 /* --------------------------------------------------------------------------------------------- */
 365 
 366 gboolean
 367 mcview_hexedit_save_changes (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 368 {
 369     int answer = 0;
 370 
 371     if (view->change_list == NULL)
 372         return TRUE;
 373 
 374     while (answer == 0)
 375     {
 376         int fp;
 377         char *text;
 378         struct hexedit_change_node *curr, *next;
 379 
 380         g_assert (view->filename_vpath != NULL);
 381 
 382         fp = mc_open (view->filename_vpath, O_WRONLY);
 383         if (fp != -1)
 384         {
 385             for (curr = view->change_list; curr != NULL; curr = next)
 386             {
 387                 next = curr->next;
 388 
 389                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 390                     || mc_write (fp, &(curr->value), 1) != 1)
 391                     goto save_error;
 392 
 393                 // delete the saved item from the change list
 394                 view->change_list = next;
 395                 view->dirty++;
 396                 mcview_set_byte (view, curr->offset, curr->value);
 397                 g_free (curr);
 398             }
 399 
 400             view->change_list = NULL;
 401 
 402             if (view->locked)
 403                 view->locked = unlock_file (view->filename_vpath) != 0;
 404 
 405             if (mc_close (fp) == -1)
 406                 message (D_ERROR, _ ("Save file"),
 407                          _ ("Error while closing the file:\n%s\n"
 408                             "Data may have been written or not"),
 409                          unix_error_string (errno));
 410 
 411             view->dirty++;
 412             return TRUE;
 413         }
 414 
 415     save_error:
 416         text = g_strdup_printf (_ ("Cannot save file:\n%s"), unix_error_string (errno));
 417         (void) mc_close (fp);
 418 
 419         answer = query_dialog (_ ("Save file"), text, D_ERROR, 2, _ ("&Retry"), _ ("&Cancel"));
 420         g_free (text);
 421     }
 422 
 423     return FALSE;
 424 }
 425 
 426 /* --------------------------------------------------------------------------------------------- */
 427 
 428 void
 429 mcview_toggle_hexedit_mode (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 430 {
 431     view->hexedit_mode = !view->hexedit_mode;
 432     view->dpy_bbar_dirty = TRUE;
 433     view->dirty++;
 434 }
 435 
 436 /* --------------------------------------------------------------------------------------------- */
 437 
 438 void
 439 mcview_hexedit_free_change_list (WView *view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 440 {
 441     struct hexedit_change_node *curr, *next;
 442 
 443     for (curr = view->change_list; curr != NULL; curr = next)
 444     {
 445         next = curr->next;
 446         g_free (curr);
 447     }
 448     view->change_list = NULL;
 449 
 450     if (view->locked)
 451         view->locked = unlock_file (view->filename_vpath) != 0;
 452 
 453     view->dirty++;
 454 }
 455 
 456 /* --------------------------------------------------------------------------------------------- */
 457 
 458 void
 459 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 460 {
 461     /* chnode always either points to the head of the list or
 462      * to one of the ->next fields in the list. The value at
 463      * this location will be overwritten with the new node.   */
 464     struct hexedit_change_node **chnode = head;
 465 
 466     while (*chnode != NULL && (*chnode)->offset < node->offset)
 467         chnode = &((*chnode)->next);
 468 
 469     node->next = *chnode;
 470     *chnode = node;
 471 }
 472 
 473 /* --------------------------------------------------------------------------------------------- */

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