2 Chattr command -- for the Midnight Commander
4 Copyright (C) 2020-2024
5 Free Software Foundation, Inc.
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/>.
27 * \brief Source: chattr command
30 /* TODO: change attributes recursively (ticket #3109) */
35 #include <sys/types.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)
64 #define CHATTRBOXES(x) ((WChattrBoxes *)(x))
66 /*** file scope type declarations ****************************************************************/
68 typedef struct WFileAttrText WFileAttrText
;
72 Widget widget
; /* base class */
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
;
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
139 gboolean state
; /* state of checkboxes */
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
},
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
},
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
},
166 #ifdef EXT4_ENCRYPT_FL
167 { EXT4_ENCRYPT_FL
, 'E', N_("Encrypted inode"), FALSE
, FALSE
},
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
},
179 { FS_NOCOW_FL
, 'C', N_("No COW"), FALSE
, FALSE
},
182 ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
183 { FS_DAX_FL
, 'x', N_("Direct access for files"), FALSE
, FALSE
},
185 #ifdef EXT4_CASEFOLD_FL
187 ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
188 { EXT4_CASEFOLD_FL
, 'F', N_("Casefolded file"), FALSE
, FALSE
},
190 #ifdef EXT4_INLINE_DATA_FL
191 { EXT4_INLINE_DATA_FL
, 'N', N_("Inode has inline data"), FALSE
, FALSE
},
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
},
199 #ifdef EXT4_VERITY_FL
201 ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
203 ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
204 { EXT4_VERITY_FL
, 'V', N_("Verity protected inode"), FALSE
, FALSE
}
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;
222 button_flags_t flags
;
226 } chattr_but
[BUTTONS
] = {
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
}
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 /* --------------------------------------------------------------------------------------------- */
261 chattr_fill_str (unsigned long attr
, char *str
)
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 /* --------------------------------------------------------------------------------------------- */
274 fileattrtext_fill (WFileAttrText
*fat
, unsigned long attr
)
276 chattr_fill_str (attr
, fat
->attrs
);
277 widget_draw (WIDGET (fat
));
280 /* --------------------------------------------------------------------------------------------- */
283 fileattrtext_callback (Widget
*w
, Widget
*sender
, widget_msg_t msg
, int parm
, void *data
)
285 WFileAttrText
*fat
= (WFileAttrText
*) w
;
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
);
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
);
323 if (color
!= DISABLED_COLOR
)
325 color
= DISABLED_COLOR
;
326 tty_setcolor (color
);
330 tty_print_char (fat
->attrs
[i
]);
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;
350 g_free (fat
->filename
);
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 };
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
);
380 /* --------------------------------------------------------------------------------------------- */
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 /* --------------------------------------------------------------------------------------------- */
393 chattr_toggle_select (const WChattrBoxes
*cb
, int Id
)
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 /* --------------------------------------------------------------------------------------------- */
409 chattrboxes_draw_scrollbar (const WChattrBoxes
*cb
)
411 const Widget
*w
= CONST_WIDGET (cb
);
416 /* Are we at the top? */
417 widget_gotoyx (w
, 0, w
->rect
.cols
);
419 tty_print_one_vline (TRUE
);
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
);
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
);
439 tty_print_one_vline (TRUE
);
441 tty_print_char ('*');
445 /* --------------------------------------------------------------------------------------------- */
448 chattrboxes_draw (WChattrBoxes
*cb
)
450 Widget
*w
= WIDGET (cb
);
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
);
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 /* --------------------------------------------------------------------------------------------- */
475 chattrboxes_rename (WChattrBoxes
*cb
)
477 Widget
*w
= WIDGET (cb
);
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 */
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
);
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
;
502 widget_set_state (w
, WST_ACTIVE
, TRUE
);
507 /* --------------------------------------------------------------------------------------------- */
510 checkboxes_save_state (const WChattrBoxes
*cb
)
515 for (i
= cb
->top
, l
= CONST_GROUP (cb
)->widgets
; l
!= NULL
; i
++, l
= g_list_next (l
))
519 m
= check_attr_mod
[i
];
520 check_attr
[m
].state
= CHECK (l
->data
)->state
;
524 /* --------------------------------------------------------------------------------------------- */
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
);
542 chattrboxes_rename (cb
);
544 else /* cb->pos > cb-top */
548 /* select next checkbox */
550 l
= g_list_next (GROUP (cb
)->current
);
551 widget_select (WIDGET (l
->data
));
557 /* --------------------------------------------------------------------------------------------- */
560 chattrboxes_page_down (WChattrBoxes
*cb
)
562 WGroup
*g
= GROUP (cb
);
565 if (cb
->pos
== check_attr_mod_num
- 1)
567 /* We are on the last checkbox.
570 l
= g_list_last (g
->widgets
);
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
;
584 cb
->pos
= check_attr_mod_num
- 1;
586 l
= g_list_last (g
->widgets
);
592 l
= g_list_nth (g
->widgets
, cb
->pos
- cb
->top
);
595 chattrboxes_rename (cb
);
598 widget_select (WIDGET (l
->data
));
603 /* --------------------------------------------------------------------------------------------- */
606 chattrboxes_end (WChattrBoxes
*cb
)
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
));
620 /* --------------------------------------------------------------------------------------------- */
623 chattrboxes_up (WChattrBoxes
*cb
)
625 if (cb
->pos
== cb
->top
)
627 /* We are on the first checkbox.
628 Keep this position. */
631 /* get out of widget */
632 return MSG_NOT_HANDLED
;
634 /* emulate scroll of checkboxes */
635 checkboxes_save_state (cb
);
638 chattrboxes_rename (cb
);
640 else /* cb->pos > cb-top */
644 /* select previous checkbox */
646 l
= g_list_previous (GROUP (cb
)->current
);
647 widget_select (WIDGET (l
->data
));
653 /* --------------------------------------------------------------------------------------------- */
656 chattrboxes_page_up (WChattrBoxes
*cb
)
658 WGroup
*g
= GROUP (cb
);
661 if (cb
->pos
== 0 && cb
->top
== 0)
663 /* We are on the first checkbox.
666 l
= g_list_first (g
->widgets
);
670 int i
= WIDGET (cb
)->rect
.lines
;
672 checkboxes_save_state (cb
);
680 l
= g_list_first (g
->widgets
);
686 l
= g_list_nth (g
->widgets
, cb
->pos
- cb
->top
);
689 chattrboxes_rename (cb
);
692 widget_select (WIDGET (l
->data
));
697 /* --------------------------------------------------------------------------------------------- */
700 chattrboxes_home (WChattrBoxes
*cb
)
704 checkboxes_save_state (cb
);
707 l
= g_list_first (GROUP (cb
)->widgets
);
708 chattrboxes_rename (cb
);
709 widget_select (WIDGET (l
->data
));
714 /* --------------------------------------------------------------------------------------------- */
717 chattrboxes_execute_cmd (WChattrBoxes
*cb
, long command
)
722 return chattrboxes_down (cb
);
725 return chattrboxes_page_down (cb
);
728 return chattrboxes_end (cb
);
731 return chattrboxes_up (cb
);
734 return chattrboxes_page_up (cb
);
737 return chattrboxes_home (cb
);
742 chattr_toggle_select (cb
, cb
->pos
); /* FIXME */
743 if (command
== CK_MarkAndDown
)
744 chattrboxes_down (cb
);
750 return MSG_NOT_HANDLED
;
754 /* --------------------------------------------------------------------------------------------- */
757 chattrboxes_key (WChattrBoxes
*cb
, int key
)
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 /* --------------------------------------------------------------------------------------------- */
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
);
778 chattrboxes_draw (cb
);
783 /* handle checkboxes */
786 i
= g_list_index (g
->widgets
, sender
);
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
;
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
))
808 i
= g_list_index (g
->widgets
, sender
);
809 cb
->pos
= cb
->top
+ i
;
817 ret
= chattrboxes_key (cb
, parm
);
818 if (ret
!= MSG_HANDLED
)
819 ret
= group_default_callback (w
, NULL
, MSG_KEY
, parm
, NULL
);
825 return chattrboxes_execute_cmd (cb
, parm
);
828 /* save all states */
829 checkboxes_save_state (cb
);
833 return group_default_callback (w
, sender
, msg
, parm
, data
);
837 /* --------------------------------------------------------------------------------------------- */
840 chattrboxes_handle_mouse_event (Widget
*w
, Gpm_Event
*event
)
844 mou
= mouse_handle_event (w
, event
);
845 if (mou
== MOU_UNHANDLED
)
846 mou
= group_handle_mouse_event (w
, event
);
851 /* --------------------------------------------------------------------------------------------- */
854 chattrboxes_mouse_callback (Widget
*w
, mouse_msg_t msg
, mouse_event_t
*event
)
856 WChattrBoxes
*cb
= CHATTRBOXES (w
);
862 case MSG_MOUSE_SCROLL_UP
:
866 case MSG_MOUSE_SCROLL_DOWN
:
867 chattrboxes_down (cb
);
871 /* return MOU_UNHANDLED */
872 event
->result
.abort
= TRUE
;
877 /* --------------------------------------------------------------------------------------------- */
879 static WChattrBoxes
*
880 chattrboxes_new (const WRect
*r
)
887 cb
= g_new0 (WChattrBoxes
, 1);
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
++)
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
;
915 /* --------------------------------------------------------------------------------------------- */
920 static gboolean i18n
= FALSE
;
923 for (i
= 0; i
< check_attr_num
; i
++)
924 check_attr
[i
].selected
= FALSE
;
931 for (i
= 0; i
< check_attr_num
; i
++)
932 if (chattr_is_modifiable (i
))
937 check_attr
[i
].text
= _(check_attr
[i
].text
);
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
++)
951 chattr_but
[i
].text
= _(chattr_but
[i
].text
);
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 /* --------------------------------------------------------------------------------------------- */
963 chattr_dlg_create (WPanel
*panel
, const char *fname
, unsigned long attr
)
965 Widget
*mw
= WIDGET (WIDGET (panel
)->owner
);
969 int checkboxes_lines
= check_attr_mod_num
;
975 const int cb_scrollbar_width
= 1;
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;
990 if (lines
>= mw
->rect
.lines
- 2)
994 dl
= lines
- (mw
->rect
.lines
- 2);
996 checkboxes_lines
-= dl
;
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
);
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
)
1014 cols
= WIDGET (file_attr
)->rect
.cols
;
1015 cols
= MIN (cols
, mw
->rect
.cols
- wx
* 2);
1016 r
.cols
= cols
+ wx
* 2;
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;
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
);
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 */
1047 MAX (cols
, chattr_but
[i
- 1].button
->rect
.cols
+ 1 + chattr_but
[i
].button
->rect
.cols
);
1050 /* adjust dialog size and button positions */
1052 if (cols
> dw
->rect
.cols
)
1057 widget_set_size_rect (dw
, &r
);
1060 cols
= dw
->rect
.x
+ dw
->rect
.cols
/ 2 + 1;
1062 for (i
= single_set
? (BUTTONS
- 2) : 0; i
< BUTTONS
; i
++)
1066 b
= chattr_but
[i
++].button
;
1068 r
.x
= cols
- r
.cols
;
1069 widget_set_size_rect (b
, &r
);
1071 b
= chattr_but
[i
].button
;
1074 widget_set_size_rect (b
, &r
);
1078 widget_select (WIDGET (cb
));
1083 /* --------------------------------------------------------------------------------------------- */
1086 chattr_done (gboolean need_update
)
1089 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
1093 /* --------------------------------------------------------------------------------------------- */
1095 static const GString
*
1096 next_file (const WPanel
*panel
)
1098 while (panel
->dir
.list
[current_file
].f
.marked
== 0)
1101 return panel
->dir
.list
[current_file
].fname
;
1104 /* --------------------------------------------------------------------------------------------- */
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
;
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
));
1121 query_dialog (MSG_ERROR
, msg
, D_ERROR
, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
1137 /* retry this file */
1142 /* stop remain files processing */
1150 /* --------------------------------------------------------------------------------------------- */
1153 do_chattr (WPanel
*panel
, const vfs_path_t
*p
, unsigned long m
)
1160 ret
= try_chattr (p
, m
);
1162 do_file_mark (panel
, current_file
, 0);
1167 /* --------------------------------------------------------------------------------------------- */
1170 chattr_apply_mask (WPanel
*panel
, vfs_path_t
*vpath
, unsigned long m
)
1174 if (!do_chattr (panel
, vpath
, m
))
1179 const GString
*fname
;
1181 fname
= next_file (panel
);
1182 vpath
= vfs_path_from_str (fname
->str
);
1183 ok
= (mc_fgetflags (vpath
, &m
) == 0);
1187 /* if current file was deleted outside mc -- try next file */
1188 /* decrease panel->marked */
1189 do_file_mark (panel
, current_file
, 0);
1197 ok
= do_chattr (panel
, vpath
, m
);
1198 vfs_path_free (vpath
, TRUE
);
1201 while (ok
&& panel
->marked
!= 0);
1204 /* --------------------------------------------------------------------------------------------- */
1205 /*** public functions ****************************************************************************/
1206 /* --------------------------------------------------------------------------------------------- */
1209 chattr_cmd (WPanel
*panel
)
1211 gboolean need_update
= FALSE
;
1212 gboolean end_chattr
= FALSE
;
1220 { /* do while any files remaining */
1223 const GString
*fname
;
1229 need_update
= FALSE
;
1232 if (panel
->marked
!= 0)
1233 fname
= next_file (panel
); /* next marked file */
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
);
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
));
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
));
1270 else if (!try_chattr (vpath
, flags
))
1272 /* stop multiple files processing */
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
;
1292 and_mask
&= ~check_attr
[i
].flags
;
1295 chattr_apply_mask (panel
, vpath
, flags
);
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
);
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
);
1330 if (panel
->marked
!= 0 && result
!= B_CANCEL
)
1332 do_file_mark (panel
, current_file
, 0);
1336 vfs_path_free (vpath
, TRUE
);
1339 while (panel
->marked
!= 0 && !end_chattr
);
1341 chattr_done (need_update
);
1344 /* --------------------------------------------------------------------------------------------- */
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
);
1356 /* --------------------------------------------------------------------------------------------- */