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-2022
   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 /*** file scope variables ************************************************************************/
  69 
  70 static const char hex_char[] = "0123456789ABCDEF";
  71 
  72 /*** file scope functions ************************************************************************/
  73 /* --------------------------------------------------------------------------------------------- */
  74 
  75 /* --------------------------------------------------------------------------------------------- */
  76 /** Determine the state of the current byte.
  77  *
  78  * @param view viewer object
  79  * @param from offset
  80  * @param curr current node
  81  */
  82 
  83 static mark_t
  84 mcview_hex_calculate_boldflag (WView * view, off_t from, struct hexedit_change_node *curr,
     /* [previous][next][first][last][top][bottom][index][help]  */
  85                                gboolean force_changed)
  86 {
  87     return (from == view->hex_cursor) ? MARK_CURSOR
  88         : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
  89         : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : 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 #ifdef HAVE_CHARSET
 115     int cont_bytes = 0;         /* number of continuation bytes remanining from current UTF-8 */
 116     gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
 117 #endif /* HAVE_CHARSET */
 118     gboolean utf8_changed = FALSE;      /* whether any of the bytes in the UTF-8 were changed */
 119 
 120     char hex_buff[10];          /* A temporary buffer for sprintf and mvwaddstr */
 121 
 122     text_start = 8 + 13 * ngroups +
 123         ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
 124 
 125     mcview_display_clean (view);
 126 
 127     /* Find the first displayable changed byte */
 128     /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
 129     from = view->dpy_start;
 130 #ifdef HAVE_CHARSET
 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 #endif /* HAVE_CHARSET */
 145     while (curr && (curr->offset < from))
 146     {
 147         curr = curr->next;
 148     }
 149 
 150     for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
 151     {
 152         int col = 0;
 153         int bytes;              /* Number of bytes already printed on the line */
 154 
 155         /* Print the hex offset */
 156         if (row >= 0)
 157         {
 158             int i;
 159 
 160             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 161             widget_gotoyx (view, r->y + row, r->x);
 162             tty_setcolor (VIEW_BOLD_COLOR);
 163             for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
 164                 tty_print_char (hex_buff[i]);
 165             tty_setcolor (VIEW_NORMAL_COLOR);
 166         }
 167 
 168         for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
 169         {
 170             int c;
 171 #ifdef HAVE_CHARSET
 172             int ch = 0;
 173 
 174             if (view->utf8)
 175             {
 176                 struct hexedit_change_node *corr = curr;
 177 
 178                 if (cont_bytes != 0)
 179                 {
 180                     /* UTF-8 continuation bytes, print a space (with proper attributes)... */
 181                     cont_bytes--;
 182                     ch = ' ';
 183                     if (cjk_right)
 184                     {
 185                         /* ... except when it'd wipe out the right half of a CJK, then print nothing */
 186                         cjk_right = FALSE;
 187                         ch = -1;
 188                     }
 189                 }
 190                 else
 191                 {
 192                     int j;
 193                     gchar utf8buf[UTF8_CHAR_LEN + 1];
 194                     int res;
 195                     int first_changed = -1;
 196 
 197                     for (j = 0; j < UTF8_CHAR_LEN; j++)
 198                     {
 199                         if (mcview_get_byte (view, from + j, &res))
 200                             utf8buf[j] = res;
 201                         else
 202                         {
 203                             utf8buf[j] = '\0';
 204                             break;
 205                         }
 206                         if (curr != NULL && from + j == curr->offset)
 207                         {
 208                             utf8buf[j] = curr->value;
 209                             if (first_changed == -1)
 210                                 first_changed = j;
 211                         }
 212                         if (curr != NULL && from + j >= curr->offset)
 213                             curr = curr->next;
 214                     }
 215                     utf8buf[UTF8_CHAR_LEN] = '\0';
 216 
 217                     /* Determine the state of the current multibyte char */
 218                     ch = g_utf8_get_char_validated (utf8buf, -1);
 219                     if (ch == -1 || ch == -2)
 220                     {
 221                         ch = '.';
 222                     }
 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 += 1;
 282             }
 283             if (col < r->cols)
 284             {
 285                 tty_print_char (hex_char[c % 16]);
 286                 col += 1;
 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 += 1;
 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 += 1;
 306                     }
 307                     if (col < r->cols)
 308                     {
 309                         tty_print_char (' ');
 310                         col += 1;
 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                 {
 329                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 330                 }
 331                 if (!g_unichar_isprint (c))
 332                     c = '.';
 333             }
 334             else if (view->utf8)
 335                 ch = convert_from_utf_to_current_c (ch, view->converter);
 336             else
 337 #endif
 338             {
 339 #ifdef HAVE_CHARSET
 340                 c = convert_to_display_c (c);
 341 #endif
 342 
 343                 if (!is_printable (c))
 344                     c = '.';
 345             }
 346 
 347             /* Print corresponding character on the text side */
 348             if (text_start + bytes < r->cols)
 349             {
 350                 widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
 351 #ifdef HAVE_CHARSET
 352                 if (view->utf8)
 353                     tty_print_anychar (ch);
 354                 else
 355 #endif
 356                     tty_print_char (c);
 357             }
 358 
 359             /* Save the cursor position for mcview_place_cursor() */
 360             if (from == view->hex_cursor && view->hexview_in_text)
 361             {
 362                 view->cursor_row = row;
 363                 view->cursor_col = text_start + bytes;
 364             }
 365         }
 366     }
 367 
 368     /* Be polite to the other functions */
 369     tty_setcolor (VIEW_NORMAL_COLOR);
 370 
 371     mcview_place_cursor (view);
 372     view->dpy_end = from;
 373 }
 374 
 375 /* --------------------------------------------------------------------------------------------- */
 376 
 377 gboolean
 378 mcview_hexedit_save_changes (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 379 {
 380     int answer = 0;
 381 
 382     if (view->change_list == NULL)
 383         return TRUE;
 384 
 385     while (answer == 0)
 386     {
 387         int fp;
 388         char *text;
 389         struct hexedit_change_node *curr, *next;
 390 
 391         g_assert (view->filename_vpath != NULL);
 392 
 393         fp = mc_open (view->filename_vpath, O_WRONLY);
 394         if (fp != -1)
 395         {
 396             for (curr = view->change_list; curr != NULL; curr = next)
 397             {
 398                 next = curr->next;
 399 
 400                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 401                     || mc_write (fp, &(curr->value), 1) != 1)
 402                     goto save_error;
 403 
 404                 /* delete the saved item from the change list */
 405                 view->change_list = next;
 406                 view->dirty++;
 407                 mcview_set_byte (view, curr->offset, curr->value);
 408                 g_free (curr);
 409             }
 410 
 411             view->change_list = NULL;
 412 
 413             if (view->locked)
 414                 view->locked = unlock_file (view->filename_vpath);
 415 
 416             if (mc_close (fp) == -1)
 417                 message (D_ERROR, _("Save file"),
 418                          _("Error while closing the file:\n%s\n"
 419                            "Data may have been written or not"), unix_error_string (errno));
 420 
 421             view->dirty++;
 422             return TRUE;
 423         }
 424 
 425       save_error:
 426         text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
 427         (void) mc_close (fp);
 428 
 429         answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
 430         g_free (text);
 431     }
 432 
 433     return FALSE;
 434 }
 435 
 436 /* --------------------------------------------------------------------------------------------- */
 437 
 438 void
 439 mcview_toggle_hexedit_mode (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 440 {
 441     view->hexedit_mode = !view->hexedit_mode;
 442     view->dpy_bbar_dirty = TRUE;
 443     view->dirty++;
 444 }
 445 
 446 /* --------------------------------------------------------------------------------------------- */
 447 
 448 void
 449 mcview_hexedit_free_change_list (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 450 {
 451     struct hexedit_change_node *curr, *next;
 452 
 453     for (curr = view->change_list; curr != NULL; curr = next)
 454     {
 455         next = curr->next;
 456         g_free (curr);
 457     }
 458     view->change_list = NULL;
 459 
 460     if (view->locked)
 461         view->locked = unlock_file (view->filename_vpath);
 462 
 463     view->dirty++;
 464 }
 465 
 466 /* --------------------------------------------------------------------------------------------- */
 467 
 468 void
 469 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 470 {
 471     /* chnode always either points to the head of the list or
 472      * to one of the ->next fields in the list. The value at
 473      * this location will be overwritten with the new node.   */
 474     struct hexedit_change_node **chnode = head;
 475 
 476     while (*chnode != NULL && (*chnode)->offset < node->offset)
 477         chnode = &((*chnode)->next);
 478 
 479     node->next = *chnode;
 480     *chnode = node;
 481 }
 482 
 483 /* --------------------------------------------------------------------------------------------- */

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