Ticket #2847: use unrar to view RAR archives if rar is unavailable.
[midnight-commander.git] / src / viewer / hex.c
blob93cc346d6a9094a63737ab16bf62ea86a644777c
1 /*
2 Internal file viewer for the Midnight Commander
3 Function for hex view
5 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
6 2004, 2005, 2006, 2007, 2009, 2011
7 The Free Software Foundation, Inc.
9 Written by:
10 Miguel de Icaza, 1994, 1995, 1998
11 Janne Kukonlehto, 1994, 1995
12 Jakub Jelinek, 1995
13 Joseph M. Hinkle, 1996
14 Norbert Warmuth, 1997
15 Pavel Machek, 1998
16 Roland Illig <roland.illig@gmx.de>, 2004, 2005
17 Slava Zanko <slavazanko@google.com>, 2009
18 Andrew Borodin <aborodin@vmail.ru>, 2009
19 Ilia Maslakov <il.smind@gmail.com>, 2009
21 This file is part of the Midnight Commander.
23 The Midnight Commander is free software: you can redistribute it
24 and/or modify it under the terms of the GNU General Public License as
25 published by the Free Software Foundation, either version 3 of the License,
26 or (at your option) any later version.
28 The Midnight Commander is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
33 You should have received a copy of the GNU General Public License
34 along with this program. If not, see <http://www.gnu.org/licenses/>.
37 #include <config.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <inttypes.h> /* uintmax_t */
43 #include "lib/global.h"
44 #include "lib/tty/tty.h"
45 #include "lib/skin.h"
46 #include "lib/vfs/vfs.h"
47 #include "lib/lock.h" /* lock_file() and unlock_file() */
48 #include "lib/util.h"
49 #include "lib/widget.h"
50 #ifdef HAVE_CHARSET
51 #include "lib/charsets.h"
52 #endif
54 #include "internal.h"
56 /*** global variables ****************************************************************************/
58 /*** file scope macro definitions ****************************************************************/
60 /*** file scope type declarations ****************************************************************/
62 typedef enum
64 MARK_NORMAL,
65 MARK_SELECTED,
66 MARK_CURSOR,
67 MARK_CHANGED
68 } mark_t;
70 /*** file scope variables ************************************************************************/
72 static const char hex_char[] = "0123456789ABCDEF";
74 /*** file scope functions ************************************************************************/
75 /* --------------------------------------------------------------------------------------------- */
77 #ifdef HAVE_CHARSET
78 static int
79 utf8_to_int (char *str, int *char_width, gboolean * result)
81 int res = -1;
82 gunichar ch;
83 gchar *next_ch = NULL;
84 int width = 0;
86 *result = TRUE;
88 if (str == NULL)
90 *result = FALSE;
91 return 0;
94 res = g_utf8_get_char_validated (str, -1);
96 if (res < 0)
97 ch = *str;
98 else
100 ch = res;
101 /* Calculate UTF-8 char width */
102 next_ch = g_utf8_next_char (str);
103 if (next_ch)
104 width = next_ch - str;
105 else
106 ch = 0;
108 *char_width = width;
109 return ch;
111 #endif /* HAVE_CHARSET */
113 /* --------------------------------------------------------------------------------------------- */
114 /*** public functions ****************************************************************************/
115 /* --------------------------------------------------------------------------------------------- */
117 void
118 mcview_display_hex (mcview_t * view)
120 const screen_dimen top = view->data_area.top;
121 const screen_dimen left = view->data_area.left;
122 const screen_dimen height = view->data_area.height;
123 const screen_dimen width = view->data_area.width;
124 const int ngroups = view->bytes_per_line / 4;
125 const screen_dimen text_start = 8 + 13 * ngroups + ((width < 80) ? 0 : (ngroups - 1 + 1));
126 /* 8 characters are used for the file offset, and every hex group
127 * takes 13 characters. On ``big'' screens, the groups are separated
128 * by an extra vertical line, and there is an extra space before the
129 * text column.
132 screen_dimen row, col;
133 off_t from;
134 int c;
135 mark_t boldflag = MARK_NORMAL;
136 struct hexedit_change_node *curr = view->change_list;
137 #ifdef HAVE_CHARSET
138 int ch = 0;
139 #endif /* HAVE_CHARSET */
141 char hex_buff[10]; /* A temporary buffer for sprintf and mvwaddstr */
142 int bytes; /* Number of bytes already printed on the line */
144 mcview_display_clean (view);
146 /* Find the first displayable changed byte */
147 from = view->dpy_start;
148 while (curr && (curr->offset < from))
150 curr = curr->next;
153 for (row = 0; mcview_get_byte (view, from, NULL) == TRUE && row < height; row++)
155 size_t i;
156 col = 0;
158 /* Print the hex offset */
159 g_snprintf (hex_buff, sizeof (hex_buff), "%08" PRIXMAX " ", (uintmax_t) from);
160 widget_move (view, top + row, left);
161 tty_setcolor (VIEW_BOLD_COLOR);
162 for (i = 0; col < width && hex_buff[i] != '\0'; i++)
164 tty_print_char (hex_buff[i]);
165 /* tty_print_char(hex_buff[i]); */
166 col += 1;
168 tty_setcolor (NORMAL_COLOR);
170 for (bytes = 0; bytes < view->bytes_per_line; bytes++, from++)
173 #ifdef HAVE_CHARSET
174 if (view->utf8)
176 char corr_buf[6 + 1];
177 int cnt, cw = 1;
178 gboolean read_res = TRUE;
179 ch = mcview_get_utf (view, from, &cw, &read_res);
180 if (!read_res)
181 break;
182 /* char width is greater 0 bytes */
183 if (cw != 0)
185 struct hexedit_change_node *corr = curr;
186 int res = g_unichar_to_utf8 (ch, (char *) corr_buf);
187 for (cnt = 0; cnt < cw; cnt++)
189 if (curr != NULL && from + cnt == curr->offset)
191 /* replace only changed bytes in array of multibyte char */
192 corr_buf[cnt] = curr->value;
193 curr = curr->next;
196 corr_buf[res] = '\0';
197 /* Determine the state of the current multibyte char */
198 ch = utf8_to_int ((char *) corr_buf, &cw, &read_res);
199 curr = corr;
202 #endif /* HAVE_CHARSET */
203 if (!mcview_get_byte (view, from, &c))
204 break;
206 /* Save the cursor position for mcview_place_cursor() */
207 if (from == view->hex_cursor && !view->hexview_in_text)
209 view->cursor_row = row;
210 view->cursor_col = col;
213 /* Determine the state of the current byte */
214 boldflag =
215 (from == view->hex_cursor) ? MARK_CURSOR
216 : (curr != NULL && from == curr->offset) ? MARK_CHANGED
217 : (view->search_start <= from &&
218 from < view->search_end) ? MARK_SELECTED : MARK_NORMAL;
220 /* Determine the value of the current byte */
221 if (curr != NULL && from == curr->offset)
223 c = curr->value;
224 curr = curr->next;
227 /* Select the color for the hex number */
228 tty_setcolor (boldflag == MARK_NORMAL ? NORMAL_COLOR :
229 boldflag == MARK_SELECTED ? VIEW_BOLD_COLOR :
230 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
231 /* boldflag == MARK_CURSOR */
232 view->hexview_in_text ? VIEW_SELECTED_COLOR : VIEW_UNDERLINED_COLOR);
234 /* Print the hex number */
235 widget_move (view, top + row, left + col);
236 if (col < width)
238 tty_print_char (hex_char[c / 16]);
239 col += 1;
241 if (col < width)
243 tty_print_char (hex_char[c % 16]);
244 col += 1;
247 /* Print the separator */
248 tty_setcolor (NORMAL_COLOR);
249 if (bytes != view->bytes_per_line - 1)
251 if (col < width)
253 tty_print_char (' ');
254 col += 1;
257 /* After every four bytes, print a group separator */
258 if (bytes % 4 == 3)
260 if (view->data_area.width >= 80 && col < width)
262 tty_print_one_vline (TRUE);
263 col += 1;
265 if (col < width)
267 tty_print_char (' ');
268 col += 1;
273 /* Select the color for the character; this differs from the
274 * hex color when boldflag == MARK_CURSOR */
275 tty_setcolor (boldflag == MARK_NORMAL ? NORMAL_COLOR :
276 boldflag == MARK_SELECTED ? VIEW_BOLD_COLOR :
277 boldflag == MARK_CHANGED ? VIEW_UNDERLINED_COLOR :
278 /* boldflag == MARK_CURSOR */
279 view->hexview_in_text ? VIEW_SELECTED_COLOR : MARKED_SELECTED_COLOR);
282 #ifdef HAVE_CHARSET
283 if (mc_global.utf8_display)
285 if (!view->utf8)
287 c = convert_from_8bit_to_utf_c ((unsigned char) c, view->converter);
289 if (!g_unichar_isprint (c))
290 c = '.';
292 else if (view->utf8)
293 ch = convert_from_utf_to_current_c (ch, view->converter);
294 else
295 #endif
297 #ifdef HAVE_CHARSET
298 c = convert_to_display_c (c);
299 #endif
301 if (!is_printable (c))
302 c = '.';
305 /* Print corresponding character on the text side */
306 if (text_start + bytes < width)
308 widget_move (view, top + row, left + text_start + bytes);
309 #ifdef HAVE_CHARSET
310 if (view->utf8)
311 tty_print_anychar (ch);
312 else
313 #endif
314 tty_print_char (c);
317 /* Save the cursor position for mcview_place_cursor() */
318 if (from == view->hex_cursor && view->hexview_in_text)
320 view->cursor_row = row;
321 view->cursor_col = text_start + bytes;
326 /* Be polite to the other functions */
327 tty_setcolor (NORMAL_COLOR);
329 mcview_place_cursor (view);
330 view->dpy_end = from;
333 /* --------------------------------------------------------------------------------------------- */
335 gboolean
336 mcview_hexedit_save_changes (mcview_t * view)
338 int answer = 0;
340 if (view->change_list == NULL)
341 return TRUE;
343 while (answer == 0)
345 int fp;
346 char *text;
347 struct hexedit_change_node *curr, *next;
349 #ifdef HAVE_ASSERT_H
350 assert (view->filename_vpath != NULL);
351 #endif
353 fp = mc_open (view->filename_vpath, O_WRONLY);
354 if (fp != -1)
356 for (curr = view->change_list; curr != NULL; curr = next)
358 next = curr->next;
360 if (mc_lseek (fp, curr->offset, SEEK_SET) == -1
361 || mc_write (fp, &(curr->value), 1) != 1)
362 goto save_error;
364 /* delete the saved item from the change list */
365 view->change_list = next;
366 view->dirty++;
367 mcview_set_byte (view, curr->offset, curr->value);
368 g_free (curr);
371 view->change_list = NULL;
373 if (view->locked)
374 view->locked = unlock_file (view->filename_vpath);
376 if (mc_close (fp) == -1)
377 message (D_ERROR, _("Save file"),
378 _("Error while closing the file:\n%s\n"
379 "Data may have been written or not"), unix_error_string (errno));
381 view->dirty++;
382 return TRUE;
385 save_error:
386 text = g_strdup_printf (_("Cannot save file:\n%s"), unix_error_string (errno));
387 (void) mc_close (fp);
389 answer = query_dialog (_("Save file"), text, D_ERROR, 2, _("&Retry"), _("&Cancel"));
390 g_free (text);
393 return FALSE;
396 /* --------------------------------------------------------------------------------------------- */
398 void
399 mcview_toggle_hexedit_mode (mcview_t * view)
401 view->hexedit_mode = !view->hexedit_mode;
402 view->dpy_bbar_dirty = TRUE;
403 view->dirty++;
406 /* --------------------------------------------------------------------------------------------- */
408 void
409 mcview_hexedit_free_change_list (mcview_t * view)
411 struct hexedit_change_node *curr, *next;
413 for (curr = view->change_list; curr != NULL; curr = next)
415 next = curr->next;
416 g_free (curr);
418 view->change_list = NULL;
420 if (view->locked)
421 view->locked = unlock_file (view->filename_vpath);
423 view->dirty++;
426 /* --------------------------------------------------------------------------------------------- */
428 void
429 mcview_enqueue_change (struct hexedit_change_node **head, struct hexedit_change_node *node)
431 /* chnode always either points to the head of the list or
432 * to one of the ->next fields in the list. The value at
433 * this location will be overwritten with the new node. */
434 struct hexedit_change_node **chnode = head;
436 while (*chnode != NULL && (*chnode)->offset < node->offset)
437 chnode = &((*chnode)->next);
439 node->next = *chnode;
440 *chnode = node;
443 /* --------------------------------------------------------------------------------------------- */