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-2019
   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
  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 screen_dimen top = view->data_area.top;
 100     const screen_dimen left = view->data_area.left;
 101     const screen_dimen height = view->data_area.height;
 102     const screen_dimen width = view->data_area.width;
 103     const int ngroups = view->bytes_per_line / 4;
 104     /* 8 characters are used for the file offset, and every hex group
 105      * takes 13 characters. Starting at width of 80 columns, the groups
 106      * are separated by an extra vertical line. Starting at width of 81,
 107      * there is an extra space before the text column. There is always a
 108      * mostly empty column on the right, to allow overflowing CJKs.
 109      */
 110     const screen_dimen text_start = 8 + 13 * ngroups +
 111         ((width < 80) ? 0 : (width == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
 112 
 113     int row;
 114     off_t from;
 115     mark_t boldflag_byte = MARK_NORMAL;
 116     mark_t boldflag_char = MARK_NORMAL;
 117     struct hexedit_change_node *curr = view->change_list;
 118 #ifdef HAVE_CHARSET
 119     int cont_bytes = 0;         /* number of continuation bytes remanining from current UTF-8 */
 120     gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
 121 #endif /* HAVE_CHARSET */
 122     gboolean utf8_changed = FALSE;      /* whether any of the bytes in the UTF-8 were changed */
 123 
 124     char hex_buff[10];          /* A temporary buffer for sprintf and mvwaddstr */
 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     row = 0;
 132 #ifdef HAVE_CHARSET
 133     if (view->utf8)
 134     {
 135         if (from >= view->bytes_per_line)
 136         {
 137             row--;
 138             from -= view->bytes_per_line;
 139         }
 140         if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
 141         {
 142             row--;
 143             from -= view->bytes_per_line;
 144         }
 145     }
 146 #endif /* HAVE_CHARSET */
 147     while (curr && (curr->offset < from))
 148     {
 149         curr = curr->next;
 150     }
 151 
 152     for (; mcview_get_byte (view, from, NULL) && row < (int) height; row++)
 153     {
 154         screen_dimen 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             size_t i;
 161 
 162             g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
 163             widget_move (view, top + row, left);
 164             tty_setcolor (VIEW_BOLD_COLOR);
 165             for (i = 0; col < width && 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                     {
 223                         ch = '.';
 224                     }
 225                     else
 226                     {
 227                         gchar *next_ch;
 228 
 229                         next_ch = g_utf8_next_char (utf8buf);
 230                         cont_bytes = next_ch - utf8buf - 1;
 231                         if (g_unichar_iswide (ch))
 232                             cjk_right = TRUE;
 233                     }
 234 
 235                     utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
 236                     curr = corr;
 237                 }
 238             }
 239 #endif /* HAVE_CHARSET */
 240 
 241             /* For negative rows, the only thing we care about is overflowing
 242              * UTF-8 continuation bytes which were handled above. */
 243             if (row < 0)
 244             {
 245                 if (curr != NULL && from == curr->offset)
 246                     curr = curr->next;
 247                 continue;
 248             }
 249 
 250             if (!mcview_get_byte (view, from, &c))
 251                 break;
 252 
 253             /* Save the cursor position for mcview_place_cursor() */
 254             if (from == view->hex_cursor && !view->hexview_in_text)
 255             {
 256                 view->cursor_row = row;
 257                 view->cursor_col = col;
 258             }
 259 
 260             /* Determine the state of the current byte */
 261             boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
 262             boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
 263 
 264             /* Determine the value of the current byte */
 265             if (curr != NULL && from == curr->offset)
 266             {
 267                 c = curr->value;
 268                 curr = curr->next;
 269             }
 270 
 271             /* Select the color for the hex number */
 272             tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 273                           boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
 274                           boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 275                           /* boldflag_byte == MARK_CURSOR */
 276                           view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
 277 
 278             /* Print the hex number */
 279             widget_move (view, top + row, left + col);
 280             if (col < width)
 281             {
 282                 tty_print_char (hex_char[c / 16]);
 283                 col += 1;
 284             }
 285             if (col < width)
 286             {
 287                 tty_print_char (hex_char[c % 16]);
 288                 col += 1;
 289             }
 290 
 291             /* Print the separator */
 292             tty_setcolor (VIEW_NORMAL_COLOR);
 293             if (bytes != view->bytes_per_line - 1)
 294             {
 295                 if (col < width)
 296                 {
 297                     tty_print_char (' ');
 298                     col += 1;
 299                 }
 300 
 301                 /* After every four bytes, print a group separator */
 302                 if (bytes % 4 == 3)
 303                 {
 304                     if (view->data_area.width >= 80 && col < width)
 305                     {
 306                         tty_print_one_vline (TRUE);
 307                         col += 1;
 308                     }
 309                     if (col < width)
 310                     {
 311                         tty_print_char (' ');
 312                         col += 1;
 313                     }
 314                 }
 315             }
 316 
 317             /* Select the color for the character; this differs from the
 318              * hex color when boldflag == MARK_CURSOR */
 319             tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
 320                           boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
 321                           boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
 322                           /* boldflag_char == MARK_CURSOR */
 323                           view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
 324 
 325 
 326 #ifdef HAVE_CHARSET
 327             if (mc_global.utf8_display)
 328             {
 329                 if (!view->utf8)
 330                 {
 331                     c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
 332                 }
 333                 if (!g_unichar_isprint (c))
 334                     c = '.';
 335             }
 336             else if (view->utf8)
 337                 ch = convert_from_utf_to_current_c (ch, view->converter);
 338             else
 339 #endif
 340             {
 341 #ifdef HAVE_CHARSET
 342                 c = convert_to_display_c (c);
 343 #endif
 344 
 345                 if (!is_printable (c))
 346                     c = '.';
 347             }
 348 
 349             /* Print corresponding character on the text side */
 350             if (text_start + bytes < width)
 351             {
 352                 widget_move (view, top + row, left + text_start + bytes);
 353 #ifdef HAVE_CHARSET
 354                 if (view->utf8)
 355                     tty_print_anychar (ch);
 356                 else
 357 #endif
 358                     tty_print_char (c);
 359             }
 360 
 361             /* Save the cursor position for mcview_place_cursor() */
 362             if (from == view->hex_cursor && view->hexview_in_text)
 363             {
 364                 view->cursor_row = row;
 365                 view->cursor_col = text_start + bytes;
 366             }
 367         }
 368     }
 369 
 370     /* Be polite to the other functions */
 371     tty_setcolor (VIEW_NORMAL_COLOR);
 372 
 373     mcview_place_cursor (view);
 374     view->dpy_end = from;
 375 }
 376 
 377 /* --------------------------------------------------------------------------------------------- */
 378 
 379 gboolean
 380 mcview_hexedit_save_changes (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 381 {
 382     int answer = 0;
 383 
 384     if (view->change_list == NULL)
 385         return TRUE;
 386 
 387     while (answer == 0)
 388     {
 389         int fp;
 390         char *text;
 391         struct hexedit_change_node *curr, *next;
 392 
 393         g_assert (view->filename_vpath != NULL);
 394 
 395         fp = mc_open (view->filename_vpath, O_WRONLY);
 396         if (fp != -1)
 397         {
 398             for (curr = view->change_list; curr != NULL; curr = next)
 399             {
 400                 next = curr->next;
 401 
 402                 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
 403                     || mc_write (fp, &(curr->value), 1) != 1)
 404                     goto save_error;
 405 
 406                 /* delete the saved item from the change list */
 407                 view->change_list = next;
 408                 view->dirty++;
 409                 mcview_set_byte (view, curr->offset, curr->value);
 410                 g_free (curr);
 411             }
 412 
 413             view->change_list = NULL;
 414 
 415             if (view->locked)
 416                 view->locked = unlock_file (view->filename_vpath);
 417 
 418             if (mc_close (fp) == -1)
 419                 message (D_ERROR, _("Save file"),
 420                          _("Error while closing the file:\n%s\n"
 421                            "Data may have been written or not"), unix_error_string (errno));
 422 
 423             view->dirty++;
 424             return TRUE;
 425         }
 426 
 427       save_error:
 428         text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
 429         (void) mc_close (fp);
 430 
 431         answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
 432         g_free (text);
 433     }
 434 
 435     return FALSE;
 436 }
 437 
 438 /* --------------------------------------------------------------------------------------------- */
 439 
 440 void
 441 mcview_toggle_hexedit_mode (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 442 {
 443     view->hexedit_mode = !view->hexedit_mode;
 444     view->dpy_bbar_dirty = TRUE;
 445     view->dirty++;
 446 }
 447 
 448 /* --------------------------------------------------------------------------------------------- */
 449 
 450 void
 451 mcview_hexedit_free_change_list (WView * view)
     /* [previous][next][first][last][top][bottom][index][help]  */
 452 {
 453     struct hexedit_change_node *curr, *next;
 454 
 455     for (curr = view->change_list; curr != NULL; curr = next)
 456     {
 457         next = curr->next;
 458         g_free (curr);
 459     }
 460     view->change_list = NULL;
 461 
 462     if (view->locked)
 463         view->locked = unlock_file (view->filename_vpath);
 464 
 465     view->dirty++;
 466 }
 467 
 468 /* --------------------------------------------------------------------------------------------- */
 469 
 470 void
 471 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
     /* [previous][next][first][last][top][bottom][index][help]  */
 472 {
 473     /* chnode always either points to the head of the list or
 474      * to one of the ->next fields in the list. The value at
 475      * this location will be overwritten with the new node.   */
 476     struct hexedit_change_node **chnode = head;
 477 
 478     while (*chnode != NULL && (*chnode)->offset < node->offset)
 479         chnode = &((*chnode)->next);
 480 
 481     node->next = *chnode;
 482     *chnode = node;
 483 }
 484 
 485 /* --------------------------------------------------------------------------------------------- */

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