Ticket #4563: support reget in file move operation.
[midnight-commander.git] / src / filemanager / chattr.c
bloba05551869b685a2f2b45cd40497461ffc356c236
1 /*
2 Chattr command -- for the Midnight Commander
4 Copyright (C) 2020-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Andrew Borodin <aborodin@vmail.ru>, 2020-2023
10 This file is part of the Midnight Commander.
12 The Midnight Commander is free software: you can redistribute it
13 and/or modify it under the terms of the GNU General Public License as
14 published by the Free Software Foundation, either version 3 of the License,
15 or (at your option) any later version.
17 The Midnight Commander is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
26 /** \file chattr.c
27 * \brief Source: chattr command
30 /* TODO: change attributes recursively (ticket #3109) */
32 #include <config.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
38 #include <ext2fs/ext2_fs.h>
40 #include "lib/global.h"
42 #include "lib/tty/tty.h" /* tty_print*() */
43 #include "lib/tty/color.h" /* tty_setcolor() */
44 #include "lib/skin.h" /* COLOR_NORMAL, DISABLED_COLOR */
45 #include "lib/vfs/vfs.h"
46 #include "lib/widget.h"
47 #include "lib/util.h" /* x_basename() */
49 #include "src/keymap.h" /* chattr_map */
51 #include "cmd.h" /* chattr_cmd(), chattr_get_as_str() */
53 /*** global variables ****************************************************************************/
55 /*** file scope macro definitions ****************************************************************/
57 #define B_MARKED B_USER
58 #define B_SETALL (B_USER + 1)
59 #define B_SETMRK (B_USER + 2)
60 #define B_CLRMRK (B_USER + 3)
62 #define BUTTONS 6
64 #define CHATTRBOXES(x) ((WChattrBoxes *)(x))
66 /*** file scope type declarations ****************************************************************/
68 typedef struct WFileAttrText WFileAttrText;
70 struct WFileAttrText
72 Widget widget; /* base class */
74 char *filename;
75 int filename_width; /* cached width of file name */
76 char attrs[32 + 1]; /* 32 bits in attributes (unsigned long) */
79 typedef struct WChattrBoxes WChattrBoxes;
81 struct WChattrBoxes
83 WGroup base; /* base class */
85 int pos; /* The current checkbox selected */
86 int top; /* The first flag displayed */
89 /*** forward declarations (file scope functions) *************************************************/
91 /*** file scope variables ************************************************************************/
93 /* see /usr/include/ext2fs/ext2_fs.h
95 * EXT2_SECRM_FL 0x00000001 -- Secure deletion
96 * EXT2_UNRM_FL 0x00000002 -- Undelete
97 * EXT2_COMPR_FL 0x00000004 -- Compress file
98 * EXT2_SYNC_FL 0x00000008 -- Synchronous updates
99 * EXT2_IMMUTABLE_FL 0x00000010 -- Immutable file
100 * EXT2_APPEND_FL 0x00000020 -- writes to file may only append
101 * EXT2_NODUMP_FL 0x00000040 -- do not dump file
102 * EXT2_NOATIME_FL 0x00000080 -- do not update atime
103 * * Reserved for compression usage...
104 * EXT2_DIRTY_FL 0x00000100
105 * EXT2_COMPRBLK_FL 0x00000200 -- One or more compressed clusters
106 * EXT2_NOCOMPR_FL 0x00000400 -- Access raw compressed data
107 * * nb: was previously EXT2_ECOMPR_FL
108 * EXT4_ENCRYPT_FL 0x00000800 -- encrypted inode
109 * * End compression flags --- maybe not all used
110 * EXT2_BTREE_FL 0x00001000 -- btree format dir
111 * EXT2_INDEX_FL 0x00001000 -- hash-indexed directory
112 * EXT2_IMAGIC_FL 0x00002000
113 * EXT3_JOURNAL_DATA_FL 0x00004000 -- file data should be journaled
114 * EXT2_NOTAIL_FL 0x00008000 -- file tail should not be merged
115 * EXT2_DIRSYNC_FL 0x00010000 -- Synchronous directory modifications
116 * EXT2_TOPDIR_FL 0x00020000 -- Top of directory hierarchies
117 * EXT4_HUGE_FILE_FL 0x00040000 -- Set to each huge file
118 * EXT4_EXTENTS_FL 0x00080000 -- Inode uses extents
119 * EXT4_VERITY_FL 0x00100000 -- Verity protected inode
120 * EXT4_EA_INODE_FL 0x00200000 -- Inode used for large EA
121 * EXT4_EOFBLOCKS_FL 0x00400000 was here, unused
122 * FS_NOCOW_FL 0x00800000 -- Do not cow file
123 * EXT4_SNAPFILE_FL 0x01000000 -- Inode is a snapshot
124 * FS_DAX_FL 0x02000000 -- Inode is DAX
125 * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted
126 * EXT4_SNAPFILE_SHRUNK_FL 0x08000000 -- Snapshot shrink has completed
127 * EXT4_INLINE_DATA_FL 0x10000000 -- Inode has inline data
128 * EXT4_PROJINHERIT_FL 0x20000000 -- Create with parents projid
129 * EXT4_CASEFOLD_FL 0x40000000 -- Casefolded file
130 * 0x80000000 -- unused yet
133 static struct
135 unsigned long flags;
136 char attr;
137 const char *text;
138 gboolean selected;
139 gboolean state; /* state of checkboxes */
140 } check_attr[] = {
141 /* *INDENT-OFF* */
142 { EXT2_SECRM_FL, 's', N_("Secure deletion"), FALSE, FALSE },
143 { EXT2_UNRM_FL, 'u', N_("Undelete"), FALSE, FALSE },
144 { EXT2_SYNC_FL, 'S', N_("Synchronous updates"), FALSE, FALSE },
145 { EXT2_DIRSYNC_FL, 'D', N_("Synchronous directory updates"), FALSE, FALSE },
146 { EXT2_IMMUTABLE_FL, 'i', N_("Immutable"), FALSE, FALSE },
147 { EXT2_APPEND_FL, 'a', N_("Append only"), FALSE, FALSE },
148 { EXT2_NODUMP_FL, 'd', N_("No dump"), FALSE, FALSE },
149 { EXT2_NOATIME_FL, 'A', N_("No update atime"), FALSE, FALSE },
150 { EXT2_COMPR_FL, 'c', N_("Compress"), FALSE, FALSE },
151 #ifdef EXT2_COMPRBLK_FL
152 /* removed in v1.43-WIP-2015-05-18
153 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
154 { EXT2_COMPRBLK_FL, 'B', N_("Compressed clusters"), FALSE, FALSE },
155 #endif
156 #ifdef EXT2_DIRTY_FL
157 /* removed in v1.43-WIP-2015-05-18
158 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
159 { EXT2_DIRTY_FL, 'Z', N_("Compressed dirty file"), FALSE, FALSE },
160 #endif
161 #ifdef EXT2_NOCOMPR_FL
162 /* removed in v1.43-WIP-2015-05-18
163 ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
164 { EXT2_NOCOMPR_FL, 'X', N_("Compression raw access"), FALSE, FALSE },
165 #endif
166 #ifdef EXT4_ENCRYPT_FL
167 { EXT4_ENCRYPT_FL, 'E', N_("Encrypted inode"), FALSE, FALSE },
168 #endif
169 { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"), FALSE, FALSE },
170 { EXT2_INDEX_FL, 'I', N_("Indexed directory"), FALSE, FALSE },
171 { EXT2_NOTAIL_FL, 't', N_("No tail merging"), FALSE, FALSE },
172 { EXT2_TOPDIR_FL, 'T', N_("Top of directory hierarchies"), FALSE, FALSE },
173 { EXT4_EXTENTS_FL, 'e', N_("Inode uses extents"), FALSE, FALSE },
174 #ifdef EXT4_HUGE_FILE_FL
175 /* removed in v1.43.9
176 ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */
177 { EXT4_HUGE_FILE_FL, 'h', N_("Huge_file"), FALSE, FALSE },
178 #endif
179 { FS_NOCOW_FL, 'C', N_("No COW"), FALSE, FALSE },
180 #ifdef FS_DAX_FL
181 /* added in v1.45.7
182 ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
183 { FS_DAX_FL, 'x', N_("Direct access for files"), FALSE, FALSE },
184 #endif
185 #ifdef EXT4_CASEFOLD_FL
186 /* added in v1.45.0
187 ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
188 { EXT4_CASEFOLD_FL, 'F', N_("Casefolded file"), FALSE, FALSE },
189 #endif
190 #ifdef EXT4_INLINE_DATA_FL
191 { EXT4_INLINE_DATA_FL, 'N', N_("Inode has inline data"), FALSE, FALSE },
192 #endif
193 #ifdef EXT4_PROJINHERIT_FL
194 /* added in v1.43-WIP-2016-05-12
195 ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07
196 97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */
197 { EXT4_PROJINHERIT_FL, 'P', N_("Project hierarchy"), FALSE, FALSE },
198 #endif
199 #ifdef EXT4_VERITY_FL
200 /* added in v1.44.4
201 ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
202 v1.44.5
203 ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
204 { EXT4_VERITY_FL, 'V', N_("Verity protected inode"), FALSE, FALSE }
205 #endif
206 /* *INDENT-ON* */
209 /* number of attributes */
210 static const size_t check_attr_num = G_N_ELEMENTS (check_attr);
212 /* modifiable attribute numbers */
213 static int check_attr_mod[32];
214 static int check_attr_mod_num = 0; /* 0..31 */
216 /* maximum width of attribute text */
217 static int check_attr_width = 0;
219 static struct
221 int ret_cmd;
222 button_flags_t flags;
223 int width;
224 const char *text;
225 Widget *button;
226 } chattr_but[BUTTONS] = {
227 /* *INDENT-OFF* */
228 /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"), NULL },
229 /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"), NULL },
230 /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"), NULL },
231 /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL },
232 /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"), NULL },
233 /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"), NULL }
234 /* *INDENT-ON* */
237 static gboolean flags_changed;
238 static int current_file;
239 static gboolean ignore_all;
241 static unsigned long and_mask, or_mask, flags;
243 static WFileAttrText *file_attr;
245 /* x-coord of widget in the dialog */
246 static const int wx = 3;
248 /* --------------------------------------------------------------------------------------------- */
249 /*** file scope functions ************************************************************************/
250 /* --------------------------------------------------------------------------------------------- */
252 static inline gboolean
253 chattr_is_modifiable (size_t i)
255 return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0);
258 /* --------------------------------------------------------------------------------------------- */
260 static void
261 chattr_fill_str (unsigned long attr, char *str)
263 size_t i;
265 for (i = 0; i < check_attr_num; i++)
266 str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-';
268 str[check_attr_num] = '\0';
271 /* --------------------------------------------------------------------------------------------- */
273 static void
274 fileattrtext_fill (WFileAttrText *fat, unsigned long attr)
276 chattr_fill_str (attr, fat->attrs);
277 widget_draw (WIDGET (fat));
280 /* --------------------------------------------------------------------------------------------- */
282 static cb_ret_t
283 fileattrtext_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
285 WFileAttrText *fat = (WFileAttrText *) w;
287 switch (msg)
289 case MSG_DRAW:
291 int color;
292 size_t i;
294 color = COLOR_NORMAL;
295 tty_setcolor (color);
297 if (w->rect.cols > fat->filename_width)
299 widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2);
300 tty_print_string (fat->filename);
302 else
304 widget_gotoyx (w, 0, 0);
305 tty_print_string (str_trunc (fat->filename, w->rect.cols));
308 /* hope that w->cols is greater than check_attr_num */
309 widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2);
310 for (i = 0; i < check_attr_num; i++)
312 /* Do not set new color for each symbol. Try to use previous color. */
313 if (chattr_is_modifiable (i))
315 if (color == DISABLED_COLOR)
317 color = COLOR_NORMAL;
318 tty_setcolor (color);
321 else
323 if (color != DISABLED_COLOR)
325 color = DISABLED_COLOR;
326 tty_setcolor (color);
330 tty_print_char (fat->attrs[i]);
332 return MSG_HANDLED;
335 case MSG_RESIZE:
337 const WRect *wo = &CONST_WIDGET (w->owner)->rect;
339 widget_default_callback (w, sender, msg, parm, data);
340 /* initially file name may be wider than screen */
341 if (fat->filename_width > wo->cols - wx * 2)
343 w->rect.x = wo->x + wx;
344 w->rect.cols = wo->cols - wx * 2;
346 return MSG_HANDLED;
349 case MSG_DESTROY:
350 g_free (fat->filename);
351 return MSG_HANDLED;
353 default:
354 return widget_default_callback (w, sender, msg, parm, data);
358 /* --------------------------------------------------------------------------------------------- */
360 static WFileAttrText *
361 fileattrtext_new (int y, int x, const char *filename, unsigned long attr)
363 WRect r = { y, x, 2, 1 };
364 WFileAttrText *fat;
365 int width;
367 width = str_term_width1 (filename);
368 r.cols = MAX (width, (int) check_attr_num);
370 fat = g_new (WFileAttrText, 1);
371 widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL);
373 fat->filename = g_strdup (filename);
374 fat->filename_width = width;
375 fileattrtext_fill (fat, attr);
377 return fat;
380 /* --------------------------------------------------------------------------------------------- */
382 static void
383 chattr_draw_select (const Widget *w, gboolean selected)
385 widget_gotoyx (w, 0, -1);
386 tty_print_char (selected ? '*' : ' ');
387 widget_gotoyx (w, 0, 1);
390 /* --------------------------------------------------------------------------------------------- */
392 static void
393 chattr_toggle_select (const WChattrBoxes *cb, int Id)
395 Widget *w;
397 /* find checkbox */
398 w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top));
400 check_attr[Id].selected = !check_attr[Id].selected;
402 tty_setcolor (COLOR_NORMAL);
403 chattr_draw_select (w, check_attr[Id].selected);
406 /* --------------------------------------------------------------------------------------------- */
408 static inline void
409 chattrboxes_draw_scrollbar (const WChattrBoxes *cb)
411 const Widget *w = CONST_WIDGET (cb);
412 int max_line;
413 int line;
414 int i;
416 /* Are we at the top? */
417 widget_gotoyx (w, 0, w->rect.cols);
418 if (cb->top == 0)
419 tty_print_one_vline (TRUE);
420 else
421 tty_print_char ('^');
423 max_line = w->rect.lines - 1;
425 /* Are we at the bottom? */
426 widget_gotoyx (w, max_line, w->rect.cols);
427 if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num)
428 tty_print_one_vline (TRUE);
429 else
430 tty_print_char ('v');
432 /* Now draw the nice relative pointer */
433 line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num;
435 for (i = 1; i < max_line; i++)
437 widget_gotoyx (w, i, w->rect.cols);
438 if (i != line)
439 tty_print_one_vline (TRUE);
440 else
441 tty_print_char ('*');
445 /* --------------------------------------------------------------------------------------------- */
447 static void
448 chattrboxes_draw (WChattrBoxes *cb)
450 Widget *w = WIDGET (cb);
451 int i;
452 GList *l;
453 const int *colors;
455 colors = widget_get_colors (w);
456 tty_setcolor (colors[DLG_COLOR_NORMAL]);
457 tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' ');
459 /* redraw checkboxes */
460 group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
462 /* draw scrollbar */
463 tty_setcolor (colors[DLG_COLOR_NORMAL]);
464 if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines)
465 chattrboxes_draw_scrollbar (cb);
467 /* mark selected checkboxes */
468 for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
469 chattr_draw_select (WIDGET (l->data), check_attr[i].selected);
472 /* --------------------------------------------------------------------------------------------- */
474 static void
475 chattrboxes_rename (WChattrBoxes *cb)
477 Widget *w = WIDGET (cb);
478 gboolean active;
479 int i;
480 GList *l;
481 char btext[BUF_SMALL]; /* FIXME: is 128 bytes enough? */
483 active = widget_get_state (w, WST_ACTIVE);
485 /* lock the group to avoid redraw of checkboxes individually */
486 if (active)
487 widget_set_state (w, WST_SUSPENDED, TRUE);
489 for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
491 WCheck *c = CHECK (l->data);
492 int m;
494 m = check_attr_mod[i];
495 g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text);
496 check_set_text (c, btext);
497 c->state = check_attr[m].state;
500 /* unlock */
501 if (active)
502 widget_set_state (w, WST_ACTIVE, TRUE);
504 widget_draw (w);
507 /* --------------------------------------------------------------------------------------------- */
509 static void
510 checkboxes_save_state (const WChattrBoxes *cb)
512 int i;
513 GList *l;
515 for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
517 int m;
519 m = check_attr_mod[i];
520 check_attr[m].state = CHECK (l->data)->state;
524 /* --------------------------------------------------------------------------------------------- */
526 static cb_ret_t
527 chattrboxes_down (WChattrBoxes *cb)
529 if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1)
531 /* We are on the last checkbox.
532 Keep this position. */
534 if (cb->pos == check_attr_mod_num - 1)
535 /* get out of widget */
536 return MSG_NOT_HANDLED;
538 /* emulate scroll of checkboxes */
539 checkboxes_save_state (cb);
540 cb->pos++;
541 cb->top++;
542 chattrboxes_rename (cb);
544 else /* cb->pos > cb-top */
546 GList *l;
548 /* select next checkbox */
549 cb->pos++;
550 l = g_list_next (GROUP (cb)->current);
551 widget_select (WIDGET (l->data));
554 return MSG_HANDLED;
557 /* --------------------------------------------------------------------------------------------- */
559 static cb_ret_t
560 chattrboxes_page_down (WChattrBoxes *cb)
562 WGroup *g = GROUP (cb);
563 GList *l;
565 if (cb->pos == check_attr_mod_num - 1)
567 /* We are on the last checkbox.
568 Keep this position.
569 Do nothing. */
570 l = g_list_last (g->widgets);
572 else
574 int i = WIDGET (cb)->rect.lines;
576 checkboxes_save_state (cb);
578 if (cb->top > check_attr_mod_num - 2 * i)
579 i = check_attr_mod_num - i - cb->top;
580 if (cb->top + i < 0)
581 i = -cb->top;
582 if (i == 0)
584 cb->pos = check_attr_mod_num - 1;
585 cb->top += i;
586 l = g_list_last (g->widgets);
588 else
590 cb->pos += i;
591 cb->top += i;
592 l = g_list_nth (g->widgets, cb->pos - cb->top);
595 chattrboxes_rename (cb);
598 widget_select (WIDGET (l->data));
600 return MSG_HANDLED;
603 /* --------------------------------------------------------------------------------------------- */
605 static cb_ret_t
606 chattrboxes_end (WChattrBoxes *cb)
608 GList *l;
610 checkboxes_save_state (cb);
611 cb->pos = check_attr_mod_num - 1;
612 cb->top = cb->pos - WIDGET (cb)->rect.lines + 1;
613 l = g_list_last (GROUP (cb)->widgets);
614 chattrboxes_rename (cb);
615 widget_select (WIDGET (l->data));
617 return MSG_HANDLED;
620 /* --------------------------------------------------------------------------------------------- */
622 static cb_ret_t
623 chattrboxes_up (WChattrBoxes *cb)
625 if (cb->pos == cb->top)
627 /* We are on the first checkbox.
628 Keep this position. */
630 if (cb->top == 0)
631 /* get out of widget */
632 return MSG_NOT_HANDLED;
634 /* emulate scroll of checkboxes */
635 checkboxes_save_state (cb);
636 cb->pos--;
637 cb->top--;
638 chattrboxes_rename (cb);
640 else /* cb->pos > cb-top */
642 GList *l;
644 /* select previous checkbox */
645 cb->pos--;
646 l = g_list_previous (GROUP (cb)->current);
647 widget_select (WIDGET (l->data));
650 return MSG_HANDLED;
653 /* --------------------------------------------------------------------------------------------- */
655 static cb_ret_t
656 chattrboxes_page_up (WChattrBoxes *cb)
658 WGroup *g = GROUP (cb);
659 GList *l;
661 if (cb->pos == 0 && cb->top == 0)
663 /* We are on the first checkbox.
664 Keep this position.
665 Do nothing. */
666 l = g_list_first (g->widgets);
668 else
670 int i = WIDGET (cb)->rect.lines;
672 checkboxes_save_state (cb);
674 if (cb->top < i)
675 i = cb->top;
676 if (i == 0)
678 cb->pos = 0;
679 cb->top -= i;
680 l = g_list_first (g->widgets);
682 else
684 cb->pos -= i;
685 cb->top -= i;
686 l = g_list_nth (g->widgets, cb->pos - cb->top);
689 chattrboxes_rename (cb);
692 widget_select (WIDGET (l->data));
694 return MSG_HANDLED;
697 /* --------------------------------------------------------------------------------------------- */
699 static cb_ret_t
700 chattrboxes_home (WChattrBoxes *cb)
702 GList *l;
704 checkboxes_save_state (cb);
705 cb->pos = 0;
706 cb->top = 0;
707 l = g_list_first (GROUP (cb)->widgets);
708 chattrboxes_rename (cb);
709 widget_select (WIDGET (l->data));
711 return MSG_HANDLED;
714 /* --------------------------------------------------------------------------------------------- */
716 static cb_ret_t
717 chattrboxes_execute_cmd (WChattrBoxes *cb, long command)
719 switch (command)
721 case CK_Down:
722 return chattrboxes_down (cb);
724 case CK_PageDown:
725 return chattrboxes_page_down (cb);
727 case CK_Bottom:
728 return chattrboxes_end (cb);
730 case CK_Up:
731 return chattrboxes_up (cb);
733 case CK_PageUp:
734 return chattrboxes_page_up (cb);
736 case CK_Top:
737 return chattrboxes_home (cb);
739 case CK_Mark:
740 case CK_MarkAndDown:
742 chattr_toggle_select (cb, cb->pos); /* FIXME */
743 if (command == CK_MarkAndDown)
744 chattrboxes_down (cb);
746 return MSG_HANDLED;
749 default:
750 return MSG_NOT_HANDLED;
754 /* --------------------------------------------------------------------------------------------- */
756 static cb_ret_t
757 chattrboxes_key (WChattrBoxes *cb, int key)
759 long command;
761 command = widget_lookup_key (WIDGET (cb), key);
762 if (command == CK_IgnoreKey)
763 return MSG_NOT_HANDLED;
764 return chattrboxes_execute_cmd (cb, command);
767 /* --------------------------------------------------------------------------------------------- */
769 static cb_ret_t
770 chattrboxes_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
772 WChattrBoxes *cb = CHATTRBOXES (w);
773 WGroup *g = GROUP (w);
775 switch (msg)
777 case MSG_DRAW:
778 chattrboxes_draw (cb);
779 return MSG_HANDLED;
781 case MSG_NOTIFY:
783 /* handle checkboxes */
784 int i;
786 i = g_list_index (g->widgets, sender);
787 if (i >= 0)
789 int m;
791 i += cb->top;
792 m = check_attr_mod[i];
793 flags ^= check_attr[m].flags;
794 fileattrtext_fill (file_attr, flags);
795 chattr_toggle_select (cb, i);
796 flags_changed = TRUE;
797 return MSG_HANDLED;
800 return MSG_NOT_HANDLED;
802 case MSG_CHANGED_FOCUS:
803 /* sender is one of chattr checkboxes */
804 if (widget_get_state (sender, WST_FOCUSED))
806 int i;
808 i = g_list_index (g->widgets, sender);
809 cb->pos = cb->top + i;
811 return MSG_HANDLED;
813 case MSG_KEY:
815 cb_ret_t ret;
817 ret = chattrboxes_key (cb, parm);
818 if (ret != MSG_HANDLED)
819 ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL);
821 return ret;
824 case MSG_ACTION:
825 return chattrboxes_execute_cmd (cb, parm);
827 case MSG_DESTROY:
828 /* save all states */
829 checkboxes_save_state (cb);
830 MC_FALLTHROUGH;
832 default:
833 return group_default_callback (w, sender, msg, parm, data);
837 /* --------------------------------------------------------------------------------------------- */
839 static int
840 chattrboxes_handle_mouse_event (Widget *w, Gpm_Event *event)
842 int mou;
844 mou = mouse_handle_event (w, event);
845 if (mou == MOU_UNHANDLED)
846 mou = group_handle_mouse_event (w, event);
848 return mou;
851 /* --------------------------------------------------------------------------------------------- */
853 static void
854 chattrboxes_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
856 WChattrBoxes *cb = CHATTRBOXES (w);
858 (void) event;
860 switch (msg)
862 case MSG_MOUSE_SCROLL_UP:
863 chattrboxes_up (cb);
864 break;
866 case MSG_MOUSE_SCROLL_DOWN:
867 chattrboxes_down (cb);
868 break;
870 default:
871 /* return MOU_UNHANDLED */
872 event->result.abort = TRUE;
873 break;
877 /* --------------------------------------------------------------------------------------------- */
879 static WChattrBoxes *
880 chattrboxes_new (const WRect *r)
882 WChattrBoxes *cb;
883 Widget *w;
884 WGroup *cbg;
885 int i;
887 cb = g_new0 (WChattrBoxes, 1);
888 w = WIDGET (cb);
889 cbg = GROUP (cb);
890 group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback);
891 w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
892 w->mouse_handler = chattrboxes_handle_mouse_event;
893 w->keymap = chattr_map;
895 /* create checkboxes */
896 for (i = 0; i < r->lines; i++)
898 int m;
899 WCheck *check;
901 m = check_attr_mod[i];
903 check = check_new (i, 0, check_attr[m].state, NULL);
904 group_add_widget (cbg, check);
907 chattrboxes_rename (cb);
909 /* select first checkbox */
910 cbg->current = cbg->widgets;
912 return cb;
915 /* --------------------------------------------------------------------------------------------- */
917 static void
918 chattr_init (void)
920 static gboolean i18n = FALSE;
921 size_t i;
923 for (i = 0; i < check_attr_num; i++)
924 check_attr[i].selected = FALSE;
926 if (i18n)
927 return;
929 i18n = TRUE;
931 for (i = 0; i < check_attr_num; i++)
932 if (chattr_is_modifiable (i))
934 int width;
936 #ifdef ENABLE_NLS
937 check_attr[i].text = _(check_attr[i].text);
938 #endif
940 check_attr_mod[check_attr_mod_num++] = i;
942 width = 4 + str_term_width1 (check_attr[i].text); /* "(Q) text " */
943 check_attr_width = MAX (check_attr_width, width);
946 check_attr_width += 1 + 3 + 1; /* mark, [x] and space */
948 for (i = 0; i < BUTTONS; i++)
950 #ifdef ENABLE_NLS
951 chattr_but[i].text = _(chattr_but[i].text);
952 #endif
954 chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */
955 if (chattr_but[i].flags == DEFPUSH_BUTTON)
956 chattr_but[i].width += 2; /* <> */
960 /* --------------------------------------------------------------------------------------------- */
962 static WDialog *
963 chattr_dlg_create (WPanel *panel, const char *fname, unsigned long attr)
965 Widget *mw = WIDGET (WIDGET (panel)->owner);
966 gboolean single_set;
967 WDialog *ch_dlg;
968 int lines, cols;
969 int checkboxes_lines = check_attr_mod_num;
970 size_t i;
971 int y;
972 Widget *dw;
973 WGroup *dg;
974 WChattrBoxes *cb;
975 const int cb_scrollbar_width = 1;
976 WRect r;
978 /* prepare to set up checkbox states */
979 for (i = 0; i < check_attr_num; i++)
980 check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0;
982 cols = check_attr_width + cb_scrollbar_width;
984 single_set = (panel->marked < 2);
986 lines = 5 + checkboxes_lines + 4;
987 if (!single_set)
988 lines += 3;
990 if (lines >= mw->rect.lines - 2)
992 int dl;
994 dl = lines - (mw->rect.lines - 2);
995 lines -= dl;
996 checkboxes_lines -= dl;
999 ch_dlg =
1000 dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors,
1001 dlg_default_callback, NULL, "[Chattr]", _("Chattr command"));
1002 dg = GROUP (ch_dlg);
1003 dw = WIDGET (ch_dlg);
1005 y = 2;
1006 file_attr = fileattrtext_new (y, wx, fname, attr);
1007 group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
1008 y += WIDGET (file_attr)->rect.lines;
1009 group_add_widget (dg, hline_new (y++, -1, -1));
1011 if (cols < WIDGET (file_attr)->rect.cols)
1013 r = dw->rect;
1014 cols = WIDGET (file_attr)->rect.cols;
1015 cols = MIN (cols, mw->rect.cols - wx * 2);
1016 r.cols = cols + wx * 2;
1017 r.lines = lines;
1018 widget_set_size_rect (dw, &r);
1021 checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines);
1022 rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols);
1023 cb = chattrboxes_new (&r);
1024 group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
1026 y += checkboxes_lines - 1;
1027 cols = 0;
1029 for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1031 if (i == 0 || i == BUTTONS - 2)
1032 group_add_widget (dg, hline_new (y++, -1, -1));
1034 chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width,
1035 chattr_but[i].ret_cmd, chattr_but[i].flags,
1036 chattr_but[i].text, NULL));
1037 group_add_widget (dg, chattr_but[i].button);
1039 i++;
1040 chattr_but[i].button =
1041 WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd,
1042 chattr_but[i].flags, chattr_but[i].text, NULL));
1043 group_add_widget (dg, chattr_but[i].button);
1045 /* two buttons in a row */
1046 cols =
1047 MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols);
1050 /* adjust dialog size and button positions */
1051 cols += 6;
1052 if (cols > dw->rect.cols)
1054 r = dw->rect;
1055 r.lines = lines;
1056 r.cols = cols;
1057 widget_set_size_rect (dw, &r);
1059 /* dialog center */
1060 cols = dw->rect.x + dw->rect.cols / 2 + 1;
1062 for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1064 Widget *b;
1066 b = chattr_but[i++].button;
1067 r = b->rect;
1068 r.x = cols - r.cols;
1069 widget_set_size_rect (b, &r);
1071 b = chattr_but[i].button;
1072 r = b->rect;
1073 r.x = cols + 1;
1074 widget_set_size_rect (b, &r);
1078 widget_select (WIDGET (cb));
1080 return ch_dlg;
1083 /* --------------------------------------------------------------------------------------------- */
1085 static void
1086 chattr_done (gboolean need_update)
1088 if (need_update)
1089 update_panels (UP_OPTIMIZE, UP_KEEPSEL);
1090 repaint_screen ();
1093 /* --------------------------------------------------------------------------------------------- */
1095 static const GString *
1096 next_file (const WPanel *panel)
1098 while (panel->dir.list[current_file].f.marked == 0)
1099 current_file++;
1101 return panel->dir.list[current_file].fname;
1104 /* --------------------------------------------------------------------------------------------- */
1106 static gboolean
1107 try_chattr (const vfs_path_t *p, unsigned long m)
1109 const char *fname = NULL;
1111 while (mc_fsetflags (p, m) == -1 && !ignore_all)
1113 int my_errno = errno;
1114 int result;
1115 char *msg;
1117 if (fname == NULL)
1118 fname = x_basename (vfs_path_as_str (p));
1119 msg = g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), fname, unix_error_string (my_errno));
1120 result =
1121 query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
1122 _("&Cancel"));
1123 g_free (msg);
1125 switch (result)
1127 case 0:
1128 /* try next file */
1129 return TRUE;
1131 case 1:
1132 ignore_all = TRUE;
1133 /* try next file */
1134 return TRUE;
1136 case 2:
1137 /* retry this file */
1138 break;
1140 case 3:
1141 default:
1142 /* stop remain files processing */
1143 return FALSE;
1147 return TRUE;
1150 /* --------------------------------------------------------------------------------------------- */
1152 static gboolean
1153 do_chattr (WPanel *panel, const vfs_path_t *p, unsigned long m)
1155 gboolean ret;
1157 m &= and_mask;
1158 m |= or_mask;
1160 ret = try_chattr (p, m);
1162 do_file_mark (panel, current_file, 0);
1164 return ret;
1167 /* --------------------------------------------------------------------------------------------- */
1169 static void
1170 chattr_apply_mask (WPanel *panel, vfs_path_t *vpath, unsigned long m)
1172 gboolean ok;
1174 if (!do_chattr (panel, vpath, m))
1175 return;
1179 const GString *fname;
1181 fname = next_file (panel);
1182 vpath = vfs_path_from_str (fname->str);
1183 ok = (mc_fgetflags (vpath, &m) == 0);
1185 if (!ok)
1187 /* if current file was deleted outside mc -- try next file */
1188 /* decrease panel->marked */
1189 do_file_mark (panel, current_file, 0);
1191 /* try next file */
1192 ok = TRUE;
1194 else
1196 flags = m;
1197 ok = do_chattr (panel, vpath, m);
1198 vfs_path_free (vpath, TRUE);
1201 while (ok && panel->marked != 0);
1204 /* --------------------------------------------------------------------------------------------- */
1205 /*** public functions ****************************************************************************/
1206 /* --------------------------------------------------------------------------------------------- */
1208 void
1209 chattr_cmd (WPanel *panel)
1211 gboolean need_update = FALSE;
1212 gboolean end_chattr = FALSE;
1214 chattr_init ();
1216 current_file = 0;
1217 ignore_all = FALSE;
1220 { /* do while any files remaining */
1221 vfs_path_t *vpath;
1222 WDialog *ch_dlg;
1223 const GString *fname;
1224 size_t i;
1225 int result;
1227 do_refresh ();
1229 need_update = FALSE;
1230 end_chattr = FALSE;
1232 if (panel->marked != 0)
1233 fname = next_file (panel); /* next marked file */
1234 else
1235 fname = panel_current_entry (panel)->fname; /* single file */
1237 vpath = vfs_path_from_str (fname->str);
1239 if (mc_fgetflags (vpath, &flags) != 0)
1241 message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str,
1242 unix_error_string (errno));
1243 vfs_path_free (vpath, TRUE);
1244 break;
1247 flags_changed = FALSE;
1249 ch_dlg = chattr_dlg_create (panel, fname->str, flags);
1250 result = dlg_run (ch_dlg);
1251 widget_destroy (WIDGET (ch_dlg));
1253 switch (result)
1255 case B_CANCEL:
1256 end_chattr = TRUE;
1257 break;
1259 case B_ENTER:
1260 if (flags_changed)
1262 if (panel->marked <= 1)
1264 /* single or last file */
1265 if (mc_fsetflags (vpath, flags) == -1 && !ignore_all)
1266 message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str,
1267 unix_error_string (errno));
1268 end_chattr = TRUE;
1270 else if (!try_chattr (vpath, flags))
1272 /* stop multiple files processing */
1273 result = B_CANCEL;
1274 end_chattr = TRUE;
1278 need_update = TRUE;
1279 break;
1281 case B_SETALL:
1282 case B_MARKED:
1283 or_mask = 0;
1284 and_mask = ~0;
1286 for (i = 0; i < check_attr_num; i++)
1287 if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL))
1289 if (check_attr[i].state)
1290 or_mask |= check_attr[i].flags;
1291 else
1292 and_mask &= ~check_attr[i].flags;
1295 chattr_apply_mask (panel, vpath, flags);
1296 need_update = TRUE;
1297 end_chattr = TRUE;
1298 break;
1300 case B_SETMRK:
1301 or_mask = 0;
1302 and_mask = ~0;
1304 for (i = 0; i < check_attr_num; i++)
1305 if (chattr_is_modifiable (i) && check_attr[i].selected)
1306 or_mask |= check_attr[i].flags;
1308 chattr_apply_mask (panel, vpath, flags);
1309 need_update = TRUE;
1310 end_chattr = TRUE;
1311 break;
1313 case B_CLRMRK:
1314 or_mask = 0;
1315 and_mask = ~0;
1317 for (i = 0; i < check_attr_num; i++)
1318 if (chattr_is_modifiable (i) && check_attr[i].selected)
1319 and_mask &= ~check_attr[i].flags;
1321 chattr_apply_mask (panel, vpath, flags);
1322 need_update = TRUE;
1323 end_chattr = TRUE;
1324 break;
1326 default:
1327 break;
1330 if (panel->marked != 0 && result != B_CANCEL)
1332 do_file_mark (panel, current_file, 0);
1333 need_update = TRUE;
1336 vfs_path_free (vpath, TRUE);
1339 while (panel->marked != 0 && !end_chattr);
1341 chattr_done (need_update);
1344 /* --------------------------------------------------------------------------------------------- */
1346 const char *
1347 chattr_get_as_str (unsigned long attr)
1349 static char str[32 + 1]; /* 32 bits in attributes (unsigned long) */
1351 chattr_fill_str (attr, str);
1353 return str;
1356 /* --------------------------------------------------------------------------------------------- */