2 Internal file viewer for the Midnight Commander
5 Copyright (C) 1994-2016
6 Free Software Foundation, Inc.
9 Miguel de Icaza, 1994, 1995, 1998
10 Janne Kukonlehto, 1994, 1995
12 Joseph M. Hinkle, 1996
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
20 This file is part of the Midnight Commander.
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.
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.
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/>.
39 #include <inttypes.h> /* uintmax_t */
41 #include "lib/global.h"
42 #include "lib/tty/tty.h"
44 #include "lib/vfs/vfs.h"
45 #include "lib/lock.h" /* lock_file() and unlock_file() */
47 #include "lib/widget.h"
49 #include "lib/charsets.h"
54 /*** global variables ****************************************************************************/
56 /*** file scope macro definitions ****************************************************************/
58 /*** file scope type declarations ****************************************************************/
68 /*** file scope variables ************************************************************************/
70 static const char hex_char
[] = "0123456789ABCDEF";
72 /*** file scope functions ************************************************************************/
73 /* --------------------------------------------------------------------------------------------- */
75 /* --------------------------------------------------------------------------------------------- */
76 /** Determine the state of the current byte.
78 * @param view viewer object
80 * @param curr current node
84 mcview_hex_calculate_boldflag (WView
* view
, off_t from
, struct hexedit_change_node
*curr
,
85 gboolean force_changed
)
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
;
92 /* --------------------------------------------------------------------------------------------- */
93 /*** public functions ****************************************************************************/
94 /* --------------------------------------------------------------------------------------------- */
97 mcview_display_hex (WView
* view
)
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.
110 const screen_dimen text_start
= 8 + 13 * ngroups
+
111 ((width
< 80) ? 0 : (width
== 80) ? (ngroups
- 1) : (ngroups
- 1 + 1));
115 mark_t boldflag_byte
= MARK_NORMAL
;
116 mark_t boldflag_char
= MARK_NORMAL
;
117 struct hexedit_change_node
*curr
= view
->change_list
;
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 */
124 char hex_buff
[10]; /* A temporary buffer for sprintf and mvwaddstr */
126 mcview_display_clean (view
);
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
;
135 if (from
>= view
->bytes_per_line
)
138 from
-= view
->bytes_per_line
;
140 if (view
->bytes_per_line
== 4 && from
>= view
->bytes_per_line
)
143 from
-= view
->bytes_per_line
;
146 #endif /* HAVE_CHARSET */
147 while (curr
&& (curr
->offset
< from
))
152 for (; mcview_get_byte (view
, from
, NULL
) && row
< (int) height
; row
++)
154 screen_dimen col
= 0;
156 int bytes
; /* Number of bytes already printed on the line */
158 /* Print the hex offset */
161 g_snprintf (hex_buff
, sizeof (hex_buff
), "%08" PRIXMAX
" ", (uintmax_t) from
);
162 widget_move (view
, top
+ row
, left
);
163 tty_setcolor (VIEW_BOLD_COLOR
);
164 for (i
= 0; col
< width
&& hex_buff
[i
] != '\0'; col
++, i
++)
165 tty_print_char (hex_buff
[i
]);
166 tty_setcolor (VIEW_NORMAL_COLOR
);
169 for (bytes
= 0; bytes
< view
->bytes_per_line
; bytes
++, from
++)
177 struct hexedit_change_node
*corr
= curr
;
181 /* UTF-8 continuation bytes, print a space (with proper attributes)... */
186 /* ... except when it'd wipe out the right half of a CJK, then print nothing */
194 gchar utf8buf
[UTF8_CHAR_LEN
+ 1];
196 int first_changed
= -1;
198 for (j
= 0; j
< UTF8_CHAR_LEN
; j
++)
200 if (mcview_get_byte (view
, from
+ j
, &res
))
207 if (curr
!= NULL
&& from
+ j
== curr
->offset
)
209 utf8buf
[j
] = curr
->value
;
210 if (first_changed
== -1)
213 if (curr
!= NULL
&& from
+ j
>= curr
->offset
)
216 utf8buf
[UTF8_CHAR_LEN
] = '\0';
218 /* Determine the state of the current multibyte char */
219 ch
= g_utf8_get_char_validated (utf8buf
, -1);
220 if (ch
== -1 || ch
== -2)
228 next_ch
= g_utf8_next_char (utf8buf
);
229 cont_bytes
= next_ch
- utf8buf
- 1;
230 if (g_unichar_iswide (ch
))
234 utf8_changed
= (first_changed
>= 0 && first_changed
<= cont_bytes
);
238 #endif /* HAVE_CHARSET */
240 /* For negative rows, the only thing we care about is overflowing
241 * UTF-8 continuation bytes which were handled above. */
244 if (curr
!= NULL
&& from
== curr
->offset
)
249 if (!mcview_get_byte (view
, from
, &c
))
252 /* Save the cursor position for mcview_place_cursor() */
253 if (from
== view
->hex_cursor
&& !view
->hexview_in_text
)
255 view
->cursor_row
= row
;
256 view
->cursor_col
= col
;
259 /* Determine the state of the current byte */
260 boldflag_byte
= mcview_hex_calculate_boldflag (view
, from
, curr
, FALSE
);
261 boldflag_char
= mcview_hex_calculate_boldflag (view
, from
, curr
, utf8_changed
);
263 /* Determine the value of the current byte */
264 if (curr
!= NULL
&& from
== curr
->offset
)
270 /* Select the color for the hex number */
271 tty_setcolor (boldflag_byte
== MARK_NORMAL
? VIEW_NORMAL_COLOR
:
272 boldflag_byte
== MARK_SELECTED
? VIEW_BOLD_COLOR
:
273 boldflag_byte
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
274 /* boldflag_byte == MARK_CURSOR */
275 view
->hexview_in_text
? VIEW_SELECTED_COLOR
: VIEW_UNDERLINED_COLOR
);
277 /* Print the hex number */
278 widget_move (view
, top
+ row
, left
+ col
);
281 tty_print_char (hex_char
[c
/ 16]);
286 tty_print_char (hex_char
[c
% 16]);
290 /* Print the separator */
291 tty_setcolor (VIEW_NORMAL_COLOR
);
292 if (bytes
!= view
->bytes_per_line
- 1)
296 tty_print_char (' ');
300 /* After every four bytes, print a group separator */
303 if (view
->data_area
.width
>= 80 && col
< width
)
305 tty_print_one_vline (TRUE
);
310 tty_print_char (' ');
316 /* Select the color for the character; this differs from the
317 * hex color when boldflag == MARK_CURSOR */
318 tty_setcolor (boldflag_char
== MARK_NORMAL
? VIEW_NORMAL_COLOR
:
319 boldflag_char
== MARK_SELECTED
? VIEW_BOLD_COLOR
:
320 boldflag_char
== MARK_CHANGED
? VIEW_UNDERLINED_COLOR
:
321 /* boldflag_char == MARK_CURSOR */
322 view
->hexview_in_text
? VIEW_SELECTED_COLOR
: MARKED_SELECTED_COLOR
);
326 if (mc_global
.utf8_display
)
330 c
= convert_from_8bit_to_utf_c ((unsigned char) c
, view
->converter
);
332 if (!g_unichar_isprint (c
))
336 ch
= convert_from_utf_to_current_c (ch
, view
->converter
);
341 c
= convert_to_display_c (c
);
344 if (!is_printable (c
))
348 /* Print corresponding character on the text side */
349 if (text_start
+ bytes
< width
)
351 widget_move (view
, top
+ row
, left
+ text_start
+ bytes
);
354 tty_print_anychar (ch
);
360 /* Save the cursor position for mcview_place_cursor() */
361 if (from
== view
->hex_cursor
&& view
->hexview_in_text
)
363 view
->cursor_row
= row
;
364 view
->cursor_col
= text_start
+ bytes
;
369 /* Be polite to the other functions */
370 tty_setcolor (VIEW_NORMAL_COLOR
);
372 mcview_place_cursor (view
);
373 view
->dpy_end
= from
;
376 /* --------------------------------------------------------------------------------------------- */
379 mcview_hexedit_save_changes (WView
* view
)
383 if (view
->change_list
== NULL
)
390 struct hexedit_change_node
*curr
, *next
;
393 assert (view
->filename_vpath
!= NULL
);
396 fp
= mc_open (view
->filename_vpath
, O_WRONLY
);
399 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
)
403 if (mc_lseek (fp
, curr
->offset
, SEEK_SET
) == -1
404 || mc_write (fp
, &(curr
->value
), 1) != 1)
407 /* delete the saved item from the change list */
408 view
->change_list
= next
;
410 mcview_set_byte (view
, curr
->offset
, curr
->value
);
414 view
->change_list
= NULL
;
417 view
->locked
= unlock_file (view
->filename_vpath
);
419 if (mc_close (fp
) == -1)
420 message (D_ERROR
, _("Save file"),
421 _("Error while closing the file:\n%s\n"
422 "Data may have been written or not"), unix_error_string (errno
));
429 text
= g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno
));
430 (void) mc_close (fp
);
432 answer
= query_dialog (_("Save file"), text
, D_ERROR
, 2, _("&Retry"), _("&Cancel"));
439 /* --------------------------------------------------------------------------------------------- */
442 mcview_toggle_hexedit_mode (WView
* view
)
444 view
->hexedit_mode
= !view
->hexedit_mode
;
445 view
->dpy_bbar_dirty
= TRUE
;
449 /* --------------------------------------------------------------------------------------------- */
452 mcview_hexedit_free_change_list (WView
* view
)
454 struct hexedit_change_node
*curr
, *next
;
456 for (curr
= view
->change_list
; curr
!= NULL
; curr
= next
)
461 view
->change_list
= NULL
;
464 view
->locked
= unlock_file (view
->filename_vpath
);
469 /* --------------------------------------------------------------------------------------------- */
472 mcview_enqueue_change (struct hexedit_change_node
**head
, struct hexedit_change_node
*node
)
474 /* chnode always either points to the head of the list or
475 * to one of the ->next fields in the list. The value at
476 * this location will be overwritten with the new node. */
477 struct hexedit_change_node
**chnode
= head
;
479 while (*chnode
!= NULL
&& (*chnode
)->offset
< node
->offset
)
480 chnode
= &((*chnode
)->next
);
482 node
->next
= *chnode
;
486 /* --------------------------------------------------------------------------------------------- */