123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- /*
- Internal file viewer for the Midnight Commander
- Function for hex view
- Copyright (C) 1994-2024
- Free Software Foundation, Inc.
- Written by:
- Miguel de Icaza, 1994, 1995, 1998
- Janne Kukonlehto, 1994, 1995
- Jakub Jelinek, 1995
- Joseph M. Hinkle, 1996
- Norbert Warmuth, 1997
- Pavel Machek, 1998
- Roland Illig <roland.illig@gmx.de>, 2004, 2005
- Slava Zanko <slavazanko@google.com>, 2009, 2013
- Andrew Borodin <aborodin@vmail.ru>, 2009-2022
- Ilia Maslakov <il.smind@gmail.com>, 2009
- This file is part of the Midnight Commander.
- The Midnight Commander is free software: you can redistribute it
- and/or modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
- The Midnight Commander is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <config.h>
- #include <errno.h>
- #include <inttypes.h> /* uintmax_t */
- #include "lib/global.h"
- #include "lib/tty/tty.h"
- #include "lib/skin.h"
- #include "lib/vfs/vfs.h"
- #include "lib/lock.h" /* lock_file() and unlock_file() */
- #include "lib/util.h"
- #include "lib/widget.h"
- #ifdef HAVE_CHARSET
- #include "lib/charsets.h"
- #endif
- #include "internal.h"
- /*** global variables ****************************************************************************/
- /*** file scope macro definitions ****************************************************************/
- /*** file scope type declarations ****************************************************************/
- typedef enum
- {
- MARK_NORMAL,
- MARK_SELECTED,
- MARK_CURSOR,
- MARK_CHANGED
- } mark_t;
- /*** forward declarations (file scope functions) *************************************************/
- /*** file scope variables ************************************************************************/
- static const char hex_char[] = "0123456789ABCDEF";
- /* --------------------------------------------------------------------------------------------- */
- /*** file scope functions ************************************************************************/
- /* --------------------------------------------------------------------------------------------- */
- /** Determine the state of the current byte.
- *
- * @param view viewer object
- * @param from offset
- * @param curr current node
- */
- static mark_t
- mcview_hex_calculate_boldflag (WView *view, off_t from, struct hexedit_change_node *curr,
- gboolean force_changed)
- {
- return (from == view->hex_cursor) ? MARK_CURSOR
- : ((curr != NULL && from == curr->offset) || force_changed) ? MARK_CHANGED
- : (view->search_start <= from && from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
- }
- /* --------------------------------------------------------------------------------------------- */
- /*** public functions ****************************************************************************/
- /* --------------------------------------------------------------------------------------------- */
- void
- mcview_display_hex (WView *view)
- {
- const WRect *r = &view->data_area;
- int ngroups = view->bytes_per_line / 4;
- /* 8 characters are used for the file offset, and every hex group
- * takes 13 characters. Starting at width of 80 columns, the groups
- * are separated by an extra vertical line. Starting at width of 81,
- * there is an extra space before the text column. There is always a
- * mostly empty column on the right, to allow overflowing CJKs.
- */
- int text_start;
- int row = 0;
- off_t from;
- mark_t boldflag_byte = MARK_NORMAL;
- mark_t boldflag_char = MARK_NORMAL;
- struct hexedit_change_node *curr = view->change_list;
- #ifdef HAVE_CHARSET
- int cont_bytes = 0; /* number of continuation bytes remanining from current UTF-8 */
- gboolean cjk_right = FALSE; /* whether the second byte of a CJK is to be processed */
- #endif /* HAVE_CHARSET */
- gboolean utf8_changed = FALSE; /* whether any of the bytes in the UTF-8 were changed */
- char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
- text_start = 8 + 13 * ngroups +
- ((r->cols < 80) ? 0 : (r->cols == 80) ? (ngroups - 1) : (ngroups - 1 + 1));
- mcview_display_clean (view);
- /* Find the first displayable changed byte */
- /* In UTF-8 mode, go back by 1 or maybe 2 lines to handle continuation bytes properly. */
- from = view->dpy_start;
- #ifdef HAVE_CHARSET
- if (view->utf8)
- {
- if (from >= view->bytes_per_line)
- {
- row--;
- from -= view->bytes_per_line;
- }
- if (view->bytes_per_line == 4 && from >= view->bytes_per_line)
- {
- row--;
- from -= view->bytes_per_line;
- }
- }
- #endif /* HAVE_CHARSET */
- while (curr && (curr->offset < from))
- {
- curr = curr->next;
- }
- for (; mcview_get_byte (view, from, NULL) && row < r->lines; row++)
- {
- int col = 0;
- int bytes; /* Number of bytes already printed on the line */
- /* Print the hex offset */
- if (row >= 0)
- {
- int i;
- g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
- widget_gotoyx (view, r->y + row, r->x);
- tty_setcolor (VIEW_BOLD_COLOR);
- for (i = 0; col < r->cols && hex_buff[i] != '\0'; col++, i++)
- tty_print_char (hex_buff[i]);
- tty_setcolor (VIEW_NORMAL_COLOR);
- }
- for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
- {
- int c;
- #ifdef HAVE_CHARSET
- int ch = 0;
- if (view->utf8)
- {
- struct hexedit_change_node *corr = curr;
- if (cont_bytes != 0)
- {
- /* UTF-8 continuation bytes, print a space (with proper attributes)... */
- cont_bytes--;
- ch = ' ';
- if (cjk_right)
- {
- /* ... except when it'd wipe out the right half of a CJK, then print nothing */
- cjk_right = FALSE;
- ch = -1;
- }
- }
- else
- {
- int j;
- gchar utf8buf[UTF8_CHAR_LEN + 1];
- int res;
- int first_changed = -1;
- for (j = 0; j < UTF8_CHAR_LEN; j++)
- {
- if (mcview_get_byte (view, from + j, &res))
- utf8buf[j] = res;
- else
- {
- utf8buf[j] = '\0';
- break;
- }
- if (curr != NULL && from + j == curr->offset)
- {
- utf8buf[j] = curr->value;
- if (first_changed == -1)
- first_changed = j;
- }
- if (curr != NULL && from + j >= curr->offset)
- curr = curr->next;
- }
- utf8buf[UTF8_CHAR_LEN] = '\0';
- /* Determine the state of the current multibyte char */
- ch = g_utf8_get_char_validated (utf8buf, -1);
- if (ch == -1 || ch == -2)
- {
- ch = '.';
- }
- else
- {
- gchar *next_ch;
- next_ch = g_utf8_next_char (utf8buf);
- cont_bytes = next_ch - utf8buf - 1;
- if (g_unichar_iswide (ch))
- cjk_right = TRUE;
- }
- utf8_changed = (first_changed >= 0 && first_changed <= cont_bytes);
- curr = corr;
- }
- }
- #endif /* HAVE_CHARSET */
- /* For negative rows, the only thing we care about is overflowing
- * UTF-8 continuation bytes which were handled above. */
- if (row < 0)
- {
- if (curr != NULL && from == curr->offset)
- curr = curr->next;
- continue;
- }
- if (!mcview_get_byte (view, from, &c))
- break;
- /* Save the cursor position for mcview_place_cursor() */
- if (from == view->hex_cursor && !view->hexview_in_text)
- {
- view->cursor_row = row;
- view->cursor_col = col;
- }
- /* Determine the state of the current byte */
- boldflag_byte = mcview_hex_calculate_boldflag (view, from, curr, FALSE);
- boldflag_char = mcview_hex_calculate_boldflag (view, from, curr, utf8_changed);
- /* Determine the value of the current byte */
- if (curr != NULL && from == curr->offset)
- {
- c = curr->value;
- curr = curr->next;
- }
- /* Select the color for the hex number */
- tty_setcolor (boldflag_byte == MARK_NORMAL ? VIEW_NORMAL_COLOR :
- boldflag_byte == MARK_SELECTED ? VIEW_BOLD_COLOR :
- boldflag_byte == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
- /* boldflag_byte == MARK_CURSOR */
- view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
- /* Print the hex number */
- widget_gotoyx (view, r->y + row, r->x + col);
- if (col < r->cols)
- {
- tty_print_char (hex_char[c / 16]);
- col += 1;
- }
- if (col < r->cols)
- {
- tty_print_char (hex_char[c % 16]);
- col += 1;
- }
- /* Print the separator */
- tty_setcolor (VIEW_NORMAL_COLOR);
- if (bytes != view->bytes_per_line - 1)
- {
- if (col < r->cols)
- {
- tty_print_char (' ');
- col += 1;
- }
- /* After every four bytes, print a group separator */
- if (bytes % 4 == 3)
- {
- if (view->data_area.cols >= 80 && col < r->cols)
- {
- tty_print_one_vline (TRUE);
- col += 1;
- }
- if (col < r->cols)
- {
- tty_print_char (' ');
- col += 1;
- }
- }
- }
- /* Select the color for the character; this differs from the
- * hex color when boldflag == MARK_CURSOR */
- tty_setcolor (boldflag_char == MARK_NORMAL ? VIEW_NORMAL_COLOR :
- boldflag_char == MARK_SELECTED ? VIEW_BOLD_COLOR :
- boldflag_char == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
- /* boldflag_char == MARK_CURSOR */
- view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
- #ifdef HAVE_CHARSET
- if (mc_global.utf8_display)
- {
- if (!view->utf8)
- {
- c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
- }
- if (!g_unichar_isprint (c))
- c = '.';
- }
- else if (view->utf8)
- ch = convert_from_utf_to_current_c (ch, view->converter);
- else
- #endif
- {
- #ifdef HAVE_CHARSET
- c = convert_to_display_c (c);
- #endif
- if (!is_printable (c))
- c = '.';
- }
- /* Print corresponding character on the text side */
- if (text_start + bytes < r->cols)
- {
- widget_gotoyx (view, r->y + row, r->x + text_start + bytes);
- #ifdef HAVE_CHARSET
- if (view->utf8)
- tty_print_anychar (ch);
- else
- #endif
- tty_print_char (c);
- }
- /* Save the cursor position for mcview_place_cursor() */
- if (from == view->hex_cursor && view->hexview_in_text)
- {
- view->cursor_row = row;
- view->cursor_col = text_start + bytes;
- }
- }
- }
- /* Be polite to the other functions */
- tty_setcolor (VIEW_NORMAL_COLOR);
- mcview_place_cursor (view);
- view->dpy_end = from;
- }
- /* --------------------------------------------------------------------------------------------- */
- gboolean
- mcview_hexedit_save_changes (WView *view)
- {
- int answer = 0;
- if (view->change_list == NULL)
- return TRUE;
- while (answer == 0)
- {
- int fp;
- char *text;
- struct hexedit_change_node *curr, *next;
- g_assert (view->filename_vpath != NULL);
- fp = mc_open (view->filename_vpath, O_WRONLY);
- if (fp != -1)
- {
- for (curr = view->change_list; curr != NULL; curr = next)
- {
- next = curr->next;
- if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
- || mc_write (fp, &(curr->value), 1) != 1)
- goto save_error;
- /* delete the saved item from the change list */
- view->change_list = next;
- view->dirty++;
- mcview_set_byte (view, curr->offset, curr->value);
- g_free (curr);
- }
- view->change_list = NULL;
- if (view->locked)
- view->locked = unlock_file (view->filename_vpath);
- if (mc_close (fp) == -1)
- message (D_ERROR, _("Save file"),
- _("Error while closing the file:\n%s\n"
- "Data may have been written or not"), unix_error_string (errno));
- view->dirty++;
- return TRUE;
- }
- save_error:
- text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
- (void) mc_close (fp);
- answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
- g_free (text);
- }
- return FALSE;
- }
- /* --------------------------------------------------------------------------------------------- */
- void
- mcview_toggle_hexedit_mode (WView *view)
- {
- view->hexedit_mode = !view->hexedit_mode;
- view->dpy_bbar_dirty = TRUE;
- view->dirty++;
- }
- /* --------------------------------------------------------------------------------------------- */
- void
- mcview_hexedit_free_change_list (WView *view)
- {
- struct hexedit_change_node *curr, *next;
- for (curr = view->change_list; curr != NULL; curr = next)
- {
- next = curr->next;
- g_free (curr);
- }
- view->change_list = NULL;
- if (view->locked)
- view->locked = unlock_file (view->filename_vpath);
- view->dirty++;
- }
- /* --------------------------------------------------------------------------------------------- */
- void
- mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
- {
- /* chnode always either points to the head of the list or
- * to one of the ->next fields in the list. The value at
- * this location will be overwritten with the new node. */
- struct hexedit_change_node **chnode = head;
- while (*chnode != NULL && (*chnode)->offset < node->offset)
- chnode = &((*chnode)->next);
- node->next = *chnode;
- *chnode = node;
- }
- /* --------------------------------------------------------------------------------------------- */
|