root/src/filemanager/chattr.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. chattr_is_modifiable
  2. chattr_fill_str
  3. fileattrtext_fill
  4. fileattrtext_callback
  5. fileattrtext_new
  6. chattr_draw_select
  7. chattr_toggle_select
  8. chattrboxes_draw_scrollbar
  9. chattrboxes_draw
  10. chattrboxes_rename
  11. checkboxes_save_state
  12. chattrboxes_down
  13. chattrboxes_page_down
  14. chattrboxes_end
  15. chattrboxes_up
  16. chattrboxes_page_up
  17. chattrboxes_home
  18. chattrboxes_execute_cmd
  19. chattrboxes_key
  20. chattrboxes_callback
  21. chattrboxes_handle_mouse_event
  22. chattrboxes_mouse_callback
  23. chattrboxes_new
  24. chattr_init
  25. chattr_dlg_create
  26. chattr_done
  27. next_file
  28. try_chattr
  29. do_chattr
  30. chattr_apply_mask
  31. chattr_cmd
  32. chattr_get_as_str

   1 /*
   2    Chattr command -- for the Midnight Commander
   3 
   4    Copyright (C) 2020-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Andrew Borodin <aborodin@vmail.ru>, 2020-2023
   9 
  10    This file is part of the Midnight Commander.
  11 
  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.
  16 
  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.
  21 
  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/>.
  24  */
  25 
  26 /** \file chattr.c
  27  *  \brief Source: chattr command
  28  */
  29 
  30 /* TODO: change attributes recursively (ticket #3109) */
  31 
  32 #include <config.h>
  33 
  34 #include <errno.h>
  35 #include <sys/types.h>
  36 #include <sys/stat.h>
  37 
  38 #include <ext2fs/ext2_fs.h>
  39 
  40 #include "lib/global.h"
  41 
  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() */
  48 
  49 #include "src/keymap.h"         /* chattr_map */
  50 
  51 #include "cmd.h"                /* chattr_cmd(), chattr_get_as_str() */
  52 
  53 /*** global variables ****************************************************************************/
  54 
  55 /*** file scope macro definitions ****************************************************************/
  56 
  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)
  61 
  62 #define BUTTONS  6
  63 
  64 #define CHATTRBOXES(x) ((WChattrBoxes *)(x))
  65 
  66 /*** file scope type declarations ****************************************************************/
  67 
  68 typedef struct WFileAttrText WFileAttrText;
  69 
  70 struct WFileAttrText
  71 {
  72     Widget widget;              /* base class */
  73 
  74     char *filename;
  75     int filename_width;         /* cached width of file name */
  76     char attrs[32 + 1];         /* 32 bits in attributes (unsigned long) */
  77 };
  78 
  79 typedef struct WChattrBoxes WChattrBoxes;
  80 
  81 struct WChattrBoxes
  82 {
  83     WGroup base;                /* base class */
  84 
  85     int pos;                    /* The current checkbox selected */
  86     int top;                    /* The first flag displayed */
  87 };
  88 
  89 /*** forward declarations (file scope functions) *************************************************/
  90 
  91 /*** file scope variables ************************************************************************/
  92 
  93 /* see /usr/include/ext2fs/ext2_fs.h
  94  *
  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
 131  */
 132 
 133 static struct
 134 {
 135     unsigned long flags;
 136     char attr;
 137     const char *text;
 138     gboolean selected;
 139     gboolean state;             /* state of checkboxes */
 140 } check_attr[] =
 141 {
 142     /* *INDENT-OFF* */
 143     { EXT2_SECRM_FL,        's', N_("Secure deletion"),               FALSE, FALSE },
 144     { EXT2_UNRM_FL,         'u', N_("Undelete"),                      FALSE, FALSE },
 145     { EXT2_SYNC_FL,         'S', N_("Synchronous updates"),           FALSE, FALSE },
 146     { EXT2_DIRSYNC_FL,      'D', N_("Synchronous directory updates"), FALSE, FALSE },
 147     { EXT2_IMMUTABLE_FL,    'i', N_("Immutable"),                     FALSE, FALSE },
 148     { EXT2_APPEND_FL,       'a', N_("Append only"),                   FALSE, FALSE },
 149     { EXT2_NODUMP_FL,       'd', N_("No dump"),                       FALSE, FALSE },
 150     { EXT2_NOATIME_FL,      'A', N_("No update atime"),               FALSE, FALSE },
 151     { EXT2_COMPR_FL,        'c', N_("Compress"),                      FALSE, FALSE },
 152 #ifdef EXT2_COMPRBLK_FL
 153     /* removed in v1.43-WIP-2015-05-18
 154        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 155     { EXT2_COMPRBLK_FL,     'B', N_("Compressed clusters"),           FALSE, FALSE },
 156 #endif
 157 #ifdef EXT2_DIRTY_FL
 158     /* removed in v1.43-WIP-2015-05-18
 159        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 160     { EXT2_DIRTY_FL,        'Z', N_("Compressed dirty file"),         FALSE, FALSE },
 161 #endif
 162 #ifdef EXT2_NOCOMPR_FL
 163     /* removed in v1.43-WIP-2015-05-18
 164        ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
 165     { EXT2_NOCOMPR_FL,      'X', N_("Compression raw access"),        FALSE, FALSE },
 166 #endif
 167 #ifdef EXT4_ENCRYPT_FL
 168     { EXT4_ENCRYPT_FL,      'E', N_("Encrypted inode"),               FALSE, FALSE },
 169 #endif
 170     { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"),                FALSE, FALSE },
 171     { EXT2_INDEX_FL,        'I', N_("Indexed directory"),             FALSE, FALSE },
 172     { EXT2_NOTAIL_FL,       't', N_("No tail merging"),               FALSE, FALSE },
 173     { EXT2_TOPDIR_FL,       'T', N_("Top of directory hierarchies"),  FALSE, FALSE },
 174     { EXT4_EXTENTS_FL,      'e', N_("Inode uses extents"),            FALSE, FALSE },
 175 #ifdef EXT4_HUGE_FILE_FL
 176     /* removed in v1.43.9
 177        ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */
 178     { EXT4_HUGE_FILE_FL,    'h', N_("Huge_file"),                     FALSE, FALSE },
 179 #endif
 180     { FS_NOCOW_FL,          'C', N_("No COW"),                        FALSE, FALSE },
 181 #ifdef FS_DAX_FL
 182     /* added in v1.45.7
 183        ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
 184     { FS_DAX_FL,            'x', N_("Direct access for files"),       FALSE, FALSE },
 185 #endif
 186 #ifdef EXT4_CASEFOLD_FL
 187     /* added in v1.45.0
 188        ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
 189     { EXT4_CASEFOLD_FL,     'F', N_("Casefolded file"),               FALSE, FALSE },
 190 #endif
 191 #ifdef EXT4_INLINE_DATA_FL
 192     { EXT4_INLINE_DATA_FL,  'N', N_("Inode has inline data"),         FALSE, FALSE },
 193 #endif
 194 #ifdef EXT4_PROJINHERIT_FL
 195     /* added in v1.43-WIP-2016-05-12
 196        ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07
 197                    97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */
 198     { EXT4_PROJINHERIT_FL,  'P', N_("Project hierarchy"),             FALSE, FALSE },
 199 #endif
 200 #ifdef EXT4_VERITY_FL
 201     /* added in v1.44.4
 202        ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
 203        v1.44.5
 204        ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
 205     { EXT4_VERITY_FL,       'V', N_("Verity protected inode"),        FALSE, FALSE }
 206 #endif
 207     /* *INDENT-ON* */
 208 };
 209 
 210 /* number of attributes */
 211 static const size_t check_attr_num = G_N_ELEMENTS (check_attr);
 212 
 213 /* modifiable attribute numbers */
 214 static int check_attr_mod[32];
 215 static int check_attr_mod_num = 0;      /* 0..31 */
 216 
 217 /* maximum width of attribute text */
 218 static int check_attr_width = 0;
 219 
 220 static struct
 221 {
 222     int ret_cmd;
 223     button_flags_t flags;
 224     int width;
 225     const char *text;
 226     Widget *button;
 227 } chattr_but[BUTTONS] =
 228 {
 229     /* *INDENT-OFF* */
 230     /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"),      NULL },
 231     /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"),   NULL },
 232     /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"),   NULL },
 233     /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL },
 234     /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"),          NULL },
 235     /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"),       NULL }
 236     /* *INDENT-ON* */
 237 };
 238 
 239 static gboolean flags_changed;
 240 static int current_file;
 241 static gboolean ignore_all;
 242 
 243 static unsigned long and_mask, or_mask, flags;
 244 
 245 static WFileAttrText *file_attr;
 246 
 247 /* x-coord of widget in the dialog */
 248 static const int wx = 3;
 249 
 250 /* --------------------------------------------------------------------------------------------- */
 251 /*** file scope functions ************************************************************************/
 252 /* --------------------------------------------------------------------------------------------- */
 253 
 254 static inline gboolean
 255 chattr_is_modifiable (size_t i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 256 {
 257     return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0);
 258 }
 259 
 260 /* --------------------------------------------------------------------------------------------- */
 261 
 262 static void
 263 chattr_fill_str (unsigned long attr, char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
 264 {
 265     size_t i;
 266 
 267     for (i = 0; i < check_attr_num; i++)
 268         str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-';
 269 
 270     str[check_attr_num] = '\0';
 271 }
 272 
 273 /* --------------------------------------------------------------------------------------------- */
 274 
 275 static void
 276 fileattrtext_fill (WFileAttrText * fat, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 277 {
 278     chattr_fill_str (attr, fat->attrs);
 279     widget_draw (WIDGET (fat));
 280 }
 281 
 282 /* --------------------------------------------------------------------------------------------- */
 283 
 284 static cb_ret_t
 285 fileattrtext_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 286 {
 287     WFileAttrText *fat = (WFileAttrText *) w;
 288 
 289     switch (msg)
 290     {
 291     case MSG_DRAW:
 292         {
 293             int color;
 294             size_t i;
 295 
 296             color = COLOR_NORMAL;
 297             tty_setcolor (color);
 298 
 299             if (w->rect.cols > fat->filename_width)
 300             {
 301                 widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2);
 302                 tty_print_string (fat->filename);
 303             }
 304             else
 305             {
 306                 widget_gotoyx (w, 0, 0);
 307                 tty_print_string (str_trunc (fat->filename, w->rect.cols));
 308             }
 309 
 310             /* hope that w->cols is greater than check_attr_num */
 311             widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2);
 312             for (i = 0; i < check_attr_num; i++)
 313             {
 314                 /* Do not set new color for each symbol. Try to use previous color. */
 315                 if (chattr_is_modifiable (i))
 316                 {
 317                     if (color == DISABLED_COLOR)
 318                     {
 319                         color = COLOR_NORMAL;
 320                         tty_setcolor (color);
 321                     }
 322                 }
 323                 else
 324                 {
 325                     if (color != DISABLED_COLOR)
 326                     {
 327                         color = DISABLED_COLOR;
 328                         tty_setcolor (color);
 329                     }
 330                 }
 331 
 332                 tty_print_char (fat->attrs[i]);
 333             }
 334             return MSG_HANDLED;
 335         }
 336 
 337     case MSG_RESIZE:
 338         {
 339             const WRect *wo = &CONST_WIDGET (w->owner)->rect;
 340 
 341             widget_default_callback (w, sender, msg, parm, data);
 342             /* initially file name may be wider than screen */
 343             if (fat->filename_width > wo->cols - wx * 2)
 344             {
 345                 w->rect.x = wo->x + wx;
 346                 w->rect.cols = wo->cols - wx * 2;
 347             }
 348             return MSG_HANDLED;
 349         }
 350 
 351     case MSG_DESTROY:
 352         g_free (fat->filename);
 353         return MSG_HANDLED;
 354 
 355     default:
 356         return widget_default_callback (w, sender, msg, parm, data);
 357     }
 358 }
 359 
 360 /* --------------------------------------------------------------------------------------------- */
 361 
 362 static WFileAttrText *
 363 fileattrtext_new (int y, int x, const char *filename, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 364 {
 365     WRect r = { y, x, 2, 1 };
 366     WFileAttrText *fat;
 367     int width;
 368 
 369     width = str_term_width1 (filename);
 370     r.cols = MAX (width, (int) check_attr_num);
 371 
 372     fat = g_new (WFileAttrText, 1);
 373     widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL);
 374 
 375     fat->filename = g_strdup (filename);
 376     fat->filename_width = width;
 377     fileattrtext_fill (fat, attr);
 378 
 379     return fat;
 380 }
 381 
 382 /* --------------------------------------------------------------------------------------------- */
 383 
 384 static void
 385 chattr_draw_select (const Widget * w, gboolean selected)
     /* [previous][next][first][last][top][bottom][index][help]  */
 386 {
 387     widget_gotoyx (w, 0, -1);
 388     tty_print_char (selected ? '*' : ' ');
 389     widget_gotoyx (w, 0, 1);
 390 }
 391 
 392 /* --------------------------------------------------------------------------------------------- */
 393 
 394 static void
 395 chattr_toggle_select (const WChattrBoxes * cb, int Id)
     /* [previous][next][first][last][top][bottom][index][help]  */
 396 {
 397     Widget *w;
 398 
 399     /* find checkbox */
 400     w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top));
 401 
 402     check_attr[Id].selected = !check_attr[Id].selected;
 403 
 404     tty_setcolor (COLOR_NORMAL);
 405     chattr_draw_select (w, check_attr[Id].selected);
 406 }
 407 
 408 /* --------------------------------------------------------------------------------------------- */
 409 
 410 static inline void
 411 chattrboxes_draw_scrollbar (const WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 412 {
 413     const Widget *w = CONST_WIDGET (cb);
 414     int max_line;
 415     int line;
 416     int i;
 417 
 418     /* Are we at the top? */
 419     widget_gotoyx (w, 0, w->rect.cols);
 420     if (cb->top == 0)
 421         tty_print_one_vline (TRUE);
 422     else
 423         tty_print_char ('^');
 424 
 425     max_line = w->rect.lines - 1;
 426 
 427     /* Are we at the bottom? */
 428     widget_gotoyx (w, max_line, w->rect.cols);
 429     if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num)
 430         tty_print_one_vline (TRUE);
 431     else
 432         tty_print_char ('v');
 433 
 434     /* Now draw the nice relative pointer */
 435     line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num;
 436 
 437     for (i = 1; i < max_line; i++)
 438     {
 439         widget_gotoyx (w, i, w->rect.cols);
 440         if (i != line)
 441             tty_print_one_vline (TRUE);
 442         else
 443             tty_print_char ('*');
 444     }
 445 }
 446 
 447 /* --------------------------------------------------------------------------------------------- */
 448 
 449 static void
 450 chattrboxes_draw (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 451 {
 452     Widget *w = WIDGET (cb);
 453     int i;
 454     GList *l;
 455     const int *colors;
 456 
 457     colors = widget_get_colors (w);
 458     tty_setcolor (colors[DLG_COLOR_NORMAL]);
 459     tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' ');
 460 
 461     /* redraw checkboxes */
 462     group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
 463 
 464     /* draw scrollbar */
 465     tty_setcolor (colors[DLG_COLOR_NORMAL]);
 466     if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines)
 467         chattrboxes_draw_scrollbar (cb);
 468 
 469     /* mark selected checkboxes */
 470     for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 471         chattr_draw_select (WIDGET (l->data), check_attr[i].selected);
 472 }
 473 
 474 /* --------------------------------------------------------------------------------------------- */
 475 
 476 static void
 477 chattrboxes_rename (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 478 {
 479     Widget *w = WIDGET (cb);
 480     gboolean active;
 481     int i;
 482     GList *l;
 483     char btext[BUF_SMALL];      /* FIXME: is 128 bytes enough? */
 484 
 485     active = widget_get_state (w, WST_ACTIVE);
 486 
 487     /* lock the group to avoid redraw of checkboxes individually */
 488     if (active)
 489         widget_set_state (w, WST_SUSPENDED, TRUE);
 490 
 491     for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 492     {
 493         WCheck *c = CHECK (l->data);
 494         int m;
 495 
 496         m = check_attr_mod[i];
 497         g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text);
 498         check_set_text (c, btext);
 499         c->state = check_attr[m].state;
 500     }
 501 
 502     /* unlock */
 503     if (active)
 504         widget_set_state (w, WST_ACTIVE, TRUE);
 505 
 506     widget_draw (w);
 507 }
 508 
 509 /* --------------------------------------------------------------------------------------------- */
 510 
 511 static void
 512 checkboxes_save_state (const WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 513 {
 514     int i;
 515     GList *l;
 516 
 517     for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
 518     {
 519         int m;
 520 
 521         m = check_attr_mod[i];
 522         check_attr[m].state = CHECK (l->data)->state;
 523     }
 524 }
 525 
 526 /* --------------------------------------------------------------------------------------------- */
 527 
 528 static cb_ret_t
 529 chattrboxes_down (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 530 {
 531     if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1)
 532     {
 533         /* We are on the last checkbox.
 534            Keep this position. */
 535 
 536         if (cb->pos == check_attr_mod_num - 1)
 537             /* get out of widget */
 538             return MSG_NOT_HANDLED;
 539 
 540         /* emulate scroll of checkboxes */
 541         checkboxes_save_state (cb);
 542         cb->pos++;
 543         cb->top++;
 544         chattrboxes_rename (cb);
 545     }
 546     else                        /* cb->pos > cb-top */
 547     {
 548         GList *l;
 549 
 550         /* select next checkbox */
 551         cb->pos++;
 552         l = g_list_next (GROUP (cb)->current);
 553         widget_select (WIDGET (l->data));
 554     }
 555 
 556     return MSG_HANDLED;
 557 }
 558 
 559 /* --------------------------------------------------------------------------------------------- */
 560 
 561 static cb_ret_t
 562 chattrboxes_page_down (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 563 {
 564     WGroup *g = GROUP (cb);
 565     GList *l;
 566 
 567     if (cb->pos == check_attr_mod_num - 1)
 568     {
 569         /* We are on the last checkbox.
 570            Keep this position.
 571            Do nothing. */
 572         l = g_list_last (g->widgets);
 573     }
 574     else
 575     {
 576         int i = WIDGET (cb)->rect.lines;
 577 
 578         checkboxes_save_state (cb);
 579 
 580         if (cb->top > check_attr_mod_num - 2 * i)
 581             i = check_attr_mod_num - i - cb->top;
 582         if (cb->top + i < 0)
 583             i = -cb->top;
 584         if (i == 0)
 585         {
 586             cb->pos = check_attr_mod_num - 1;
 587             cb->top += i;
 588             l = g_list_last (g->widgets);
 589         }
 590         else
 591         {
 592             cb->pos += i;
 593             cb->top += i;
 594             l = g_list_nth (g->widgets, cb->pos - cb->top);
 595         }
 596 
 597         chattrboxes_rename (cb);
 598     }
 599 
 600     widget_select (WIDGET (l->data));
 601 
 602     return MSG_HANDLED;
 603 }
 604 
 605 /* --------------------------------------------------------------------------------------------- */
 606 
 607 static cb_ret_t
 608 chattrboxes_end (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 609 {
 610     GList *l;
 611 
 612     checkboxes_save_state (cb);
 613     cb->pos = check_attr_mod_num - 1;
 614     cb->top = cb->pos - WIDGET (cb)->rect.lines + 1;
 615     l = g_list_last (GROUP (cb)->widgets);
 616     chattrboxes_rename (cb);
 617     widget_select (WIDGET (l->data));
 618 
 619     return MSG_HANDLED;
 620 }
 621 
 622 /* --------------------------------------------------------------------------------------------- */
 623 
 624 static cb_ret_t
 625 chattrboxes_up (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 626 {
 627     if (cb->pos == cb->top)
 628     {
 629         /* We are on the first checkbox.
 630            Keep this position. */
 631 
 632         if (cb->top == 0)
 633             /* get out of widget */
 634             return MSG_NOT_HANDLED;
 635 
 636         /* emulate scroll of checkboxes */
 637         checkboxes_save_state (cb);
 638         cb->pos--;
 639         cb->top--;
 640         chattrboxes_rename (cb);
 641     }
 642     else                        /* cb->pos > cb-top */
 643     {
 644         GList *l;
 645 
 646         /* select previous checkbox */
 647         cb->pos--;
 648         l = g_list_previous (GROUP (cb)->current);
 649         widget_select (WIDGET (l->data));
 650     }
 651 
 652     return MSG_HANDLED;
 653 }
 654 
 655 /* --------------------------------------------------------------------------------------------- */
 656 
 657 static cb_ret_t
 658 chattrboxes_page_up (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 659 {
 660     WGroup *g = GROUP (cb);
 661     GList *l;
 662 
 663     if (cb->pos == 0 && cb->top == 0)
 664     {
 665         /* We are on the first checkbox.
 666            Keep this position.
 667            Do nothing. */
 668         l = g_list_first (g->widgets);
 669     }
 670     else
 671     {
 672         int i = WIDGET (cb)->rect.lines;
 673 
 674         checkboxes_save_state (cb);
 675 
 676         if (cb->top < i)
 677             i = cb->top;
 678         if (i == 0)
 679         {
 680             cb->pos = 0;
 681             cb->top -= i;
 682             l = g_list_first (g->widgets);
 683         }
 684         else
 685         {
 686             cb->pos -= i;
 687             cb->top -= i;
 688             l = g_list_nth (g->widgets, cb->pos - cb->top);
 689         }
 690 
 691         chattrboxes_rename (cb);
 692     }
 693 
 694     widget_select (WIDGET (l->data));
 695 
 696     return MSG_HANDLED;
 697 }
 698 
 699 /* --------------------------------------------------------------------------------------------- */
 700 
 701 static cb_ret_t
 702 chattrboxes_home (WChattrBoxes * cb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 703 {
 704     GList *l;
 705 
 706     checkboxes_save_state (cb);
 707     cb->pos = 0;
 708     cb->top = 0;
 709     l = g_list_first (GROUP (cb)->widgets);
 710     chattrboxes_rename (cb);
 711     widget_select (WIDGET (l->data));
 712 
 713     return MSG_HANDLED;
 714 }
 715 
 716 /* --------------------------------------------------------------------------------------------- */
 717 
 718 static cb_ret_t
 719 chattrboxes_execute_cmd (WChattrBoxes * cb, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 720 {
 721     switch (command)
 722     {
 723     case CK_Down:
 724         return chattrboxes_down (cb);
 725 
 726     case CK_PageDown:
 727         return chattrboxes_page_down (cb);
 728 
 729     case CK_Bottom:
 730         return chattrboxes_end (cb);
 731 
 732     case CK_Up:
 733         return chattrboxes_up (cb);
 734 
 735     case CK_PageUp:
 736         return chattrboxes_page_up (cb);
 737 
 738     case CK_Top:
 739         return chattrboxes_home (cb);
 740 
 741     case CK_Mark:
 742     case CK_MarkAndDown:
 743         {
 744             chattr_toggle_select (cb, cb->pos); /* FIXME */
 745             if (command == CK_MarkAndDown)
 746                 chattrboxes_down (cb);
 747 
 748             return MSG_HANDLED;
 749         }
 750 
 751     default:
 752         return MSG_NOT_HANDLED;
 753     }
 754 }
 755 
 756 /* --------------------------------------------------------------------------------------------- */
 757 
 758 static cb_ret_t
 759 chattrboxes_key (WChattrBoxes * cb, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 760 {
 761     long command;
 762 
 763     command = widget_lookup_key (WIDGET (cb), key);
 764     if (command == CK_IgnoreKey)
 765         return MSG_NOT_HANDLED;
 766     return chattrboxes_execute_cmd (cb, command);
 767 }
 768 
 769 /* --------------------------------------------------------------------------------------------- */
 770 
 771 static cb_ret_t
 772 chattrboxes_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 773 {
 774     WChattrBoxes *cb = CHATTRBOXES (w);
 775     WGroup *g = GROUP (w);
 776 
 777     switch (msg)
 778     {
 779     case MSG_DRAW:
 780         chattrboxes_draw (cb);
 781         return MSG_HANDLED;
 782 
 783     case MSG_NOTIFY:
 784         {
 785             /* handle checkboxes */
 786             int i;
 787 
 788             i = g_list_index (g->widgets, sender);
 789             if (i >= 0)
 790             {
 791                 int m;
 792 
 793                 i += cb->top;
 794                 m = check_attr_mod[i];
 795                 flags ^= check_attr[m].flags;
 796                 fileattrtext_fill (file_attr, flags);
 797                 chattr_toggle_select (cb, i);
 798                 flags_changed = TRUE;
 799                 return MSG_HANDLED;
 800             }
 801         }
 802         return MSG_NOT_HANDLED;
 803 
 804     case MSG_CHANGED_FOCUS:
 805         /* sender is one of chattr checkboxes */
 806         if (widget_get_state (sender, WST_FOCUSED))
 807         {
 808             int i;
 809 
 810             i = g_list_index (g->widgets, sender);
 811             cb->pos = cb->top + i;
 812         }
 813         return MSG_HANDLED;
 814 
 815     case MSG_KEY:
 816         {
 817             cb_ret_t ret;
 818 
 819             ret = chattrboxes_key (cb, parm);
 820             if (ret != MSG_HANDLED)
 821                 ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL);
 822 
 823             return ret;
 824         }
 825 
 826     case MSG_ACTION:
 827         return chattrboxes_execute_cmd (cb, parm);
 828 
 829     case MSG_DESTROY:
 830         /* save all states */
 831         checkboxes_save_state (cb);
 832         MC_FALLTHROUGH;
 833 
 834     default:
 835         return group_default_callback (w, sender, msg, parm, data);
 836     }
 837 }
 838 
 839 /* --------------------------------------------------------------------------------------------- */
 840 
 841 static int
 842 chattrboxes_handle_mouse_event (Widget * w, Gpm_Event * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 843 {
 844     int mou;
 845 
 846     mou = mouse_handle_event (w, event);
 847     if (mou == MOU_UNHANDLED)
 848         mou = group_handle_mouse_event (w, event);
 849 
 850     return mou;
 851 }
 852 
 853 /* --------------------------------------------------------------------------------------------- */
 854 
 855 static void
 856 chattrboxes_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
 857 {
 858     WChattrBoxes *cb = CHATTRBOXES (w);
 859 
 860     (void) event;
 861 
 862     switch (msg)
 863     {
 864     case MSG_MOUSE_SCROLL_UP:
 865         chattrboxes_up (cb);
 866         break;
 867 
 868     case MSG_MOUSE_SCROLL_DOWN:
 869         chattrboxes_down (cb);
 870         break;
 871 
 872     default:
 873         /* return MOU_UNHANDLED */
 874         event->result.abort = TRUE;
 875         break;
 876     }
 877 }
 878 
 879 /* --------------------------------------------------------------------------------------------- */
 880 
 881 static WChattrBoxes *
 882 chattrboxes_new (const WRect * r)
     /* [previous][next][first][last][top][bottom][index][help]  */
 883 {
 884     WChattrBoxes *cb;
 885     Widget *w;
 886     WGroup *cbg;
 887     int i;
 888 
 889     cb = g_new0 (WChattrBoxes, 1);
 890     w = WIDGET (cb);
 891     cbg = GROUP (cb);
 892     group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback);
 893     w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
 894     w->mouse_handler = chattrboxes_handle_mouse_event;
 895     w->keymap = chattr_map;
 896 
 897     /* create checkboxes */
 898     for (i = 0; i < r->lines; i++)
 899     {
 900         int m;
 901         WCheck *check;
 902 
 903         m = check_attr_mod[i];
 904 
 905         check = check_new (i, 0, check_attr[m].state, NULL);
 906         group_add_widget (cbg, check);
 907     }
 908 
 909     chattrboxes_rename (cb);
 910 
 911     /* select first checkbox */
 912     cbg->current = cbg->widgets;
 913 
 914     return cb;
 915 }
 916 
 917 /* --------------------------------------------------------------------------------------------- */
 918 
 919 static void
 920 chattr_init (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 921 {
 922     static gboolean i18n = FALSE;
 923     size_t i;
 924 
 925     for (i = 0; i < check_attr_num; i++)
 926         check_attr[i].selected = FALSE;
 927 
 928     if (i18n)
 929         return;
 930 
 931     i18n = TRUE;
 932 
 933     for (i = 0; i < check_attr_num; i++)
 934         if (chattr_is_modifiable (i))
 935         {
 936             int width;
 937 
 938 #ifdef ENABLE_NLS
 939             check_attr[i].text = _(check_attr[i].text);
 940 #endif
 941 
 942             check_attr_mod[check_attr_mod_num++] = i;
 943 
 944             width = 4 + str_term_width1 (check_attr[i].text);   /* "(Q) text " */
 945             check_attr_width = MAX (check_attr_width, width);
 946         }
 947 
 948     check_attr_width += 1 + 3 + 1;      /* mark, [x] and space */
 949 
 950     for (i = 0; i < BUTTONS; i++)
 951     {
 952 #ifdef ENABLE_NLS
 953         chattr_but[i].text = _(chattr_but[i].text);
 954 #endif
 955 
 956         chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */
 957         if (chattr_but[i].flags == DEFPUSH_BUTTON)
 958             chattr_but[i].width += 2;   /* <> */
 959     }
 960 }
 961 
 962 /* --------------------------------------------------------------------------------------------- */
 963 
 964 static WDialog *
 965 chattr_dlg_create (WPanel * panel, const char *fname, unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
 966 {
 967     Widget *mw = WIDGET (WIDGET (panel)->owner);
 968     gboolean single_set;
 969     WDialog *ch_dlg;
 970     int lines, cols;
 971     int checkboxes_lines = check_attr_mod_num;
 972     size_t i;
 973     int y;
 974     Widget *dw;
 975     WGroup *dg;
 976     WChattrBoxes *cb;
 977     const int cb_scrollbar_width = 1;
 978     WRect r;
 979 
 980     /* prepare to set up checkbox states */
 981     for (i = 0; i < check_attr_num; i++)
 982         check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0;
 983 
 984     cols = check_attr_width + cb_scrollbar_width;
 985 
 986     single_set = (panel->marked < 2);
 987 
 988     lines = 5 + checkboxes_lines + 4;
 989     if (!single_set)
 990         lines += 3;
 991 
 992     if (lines >= mw->rect.lines - 2)
 993     {
 994         int dl;
 995 
 996         dl = lines - (mw->rect.lines - 2);
 997         lines -= dl;
 998         checkboxes_lines -= dl;
 999     }
1000 
1001     ch_dlg =
1002         dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors,
1003                     dlg_default_callback, NULL, "[Chattr]", _("Chattr command"));
1004     dg = GROUP (ch_dlg);
1005     dw = WIDGET (ch_dlg);
1006 
1007     y = 2;
1008     file_attr = fileattrtext_new (y, wx, fname, attr);
1009     group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
1010     y += WIDGET (file_attr)->rect.lines;
1011     group_add_widget (dg, hline_new (y++, -1, -1));
1012 
1013     if (cols < WIDGET (file_attr)->rect.cols)
1014     {
1015         r = dw->rect;
1016         cols = WIDGET (file_attr)->rect.cols;
1017         cols = MIN (cols, mw->rect.cols - wx * 2);
1018         r.cols = cols + wx * 2;
1019         r.lines = lines;
1020         widget_set_size_rect (dw, &r);
1021     }
1022 
1023     checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines);
1024     rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols);
1025     cb = chattrboxes_new (&r);
1026     group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
1027 
1028     y += checkboxes_lines - 1;
1029     cols = 0;
1030 
1031     for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1032     {
1033         if (i == 0 || i == BUTTONS - 2)
1034             group_add_widget (dg, hline_new (y++, -1, -1));
1035 
1036         chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width,
1037                                                    chattr_but[i].ret_cmd, chattr_but[i].flags,
1038                                                    chattr_but[i].text, NULL));
1039         group_add_widget (dg, chattr_but[i].button);
1040 
1041         i++;
1042         chattr_but[i].button =
1043             WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd,
1044                                 chattr_but[i].flags, chattr_but[i].text, NULL));
1045         group_add_widget (dg, chattr_but[i].button);
1046 
1047         /* two buttons in a row */
1048         cols =
1049             MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols);
1050     }
1051 
1052     /* adjust dialog size and button positions */
1053     cols += 6;
1054     if (cols > dw->rect.cols)
1055     {
1056         r = dw->rect;
1057         r.lines = lines;
1058         r.cols = cols;
1059         widget_set_size_rect (dw, &r);
1060 
1061         /* dialog center */
1062         cols = dw->rect.x + dw->rect.cols / 2 + 1;
1063 
1064         for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
1065         {
1066             Widget *b;
1067 
1068             b = chattr_but[i++].button;
1069             r = b->rect;
1070             r.x = cols - r.cols;
1071             widget_set_size_rect (b, &r);
1072 
1073             b = chattr_but[i].button;
1074             r = b->rect;
1075             r.x = cols + 1;
1076             widget_set_size_rect (b, &r);
1077         }
1078     }
1079 
1080     widget_select (WIDGET (cb));
1081 
1082     return ch_dlg;
1083 }
1084 
1085 /* --------------------------------------------------------------------------------------------- */
1086 
1087 static void
1088 chattr_done (gboolean need_update)
     /* [previous][next][first][last][top][bottom][index][help]  */
1089 {
1090     if (need_update)
1091         update_panels (UP_OPTIMIZE, UP_KEEPSEL);
1092     repaint_screen ();
1093 }
1094 
1095 /* --------------------------------------------------------------------------------------------- */
1096 
1097 static const GString *
1098 next_file (const WPanel * panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1099 {
1100     while (panel->dir.list[current_file].f.marked == 0)
1101         current_file++;
1102 
1103     return panel->dir.list[current_file].fname;
1104 }
1105 
1106 /* --------------------------------------------------------------------------------------------- */
1107 
1108 static gboolean
1109 try_chattr (const vfs_path_t * p, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1110 {
1111     const char *fname = NULL;
1112 
1113     while (mc_fsetflags (p, m) == -1 && !ignore_all)
1114     {
1115         int my_errno = errno;
1116         int result;
1117         char *msg;
1118 
1119         if (fname == NULL)
1120             fname = x_basename (vfs_path_as_str (p));
1121         msg = g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), fname, unix_error_string (my_errno));
1122         result =
1123             query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
1124                           _("&Cancel"));
1125         g_free (msg);
1126 
1127         switch (result)
1128         {
1129         case 0:
1130             /* try next file */
1131             return TRUE;
1132 
1133         case 1:
1134             ignore_all = TRUE;
1135             /* try next file */
1136             return TRUE;
1137 
1138         case 2:
1139             /* retry this file */
1140             break;
1141 
1142         case 3:
1143         default:
1144             /* stop remain files processing */
1145             return FALSE;
1146         }
1147     }
1148 
1149     return TRUE;
1150 }
1151 
1152 /* --------------------------------------------------------------------------------------------- */
1153 
1154 static gboolean
1155 do_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1156 {
1157     gboolean ret;
1158 
1159     m &= and_mask;
1160     m |= or_mask;
1161 
1162     ret = try_chattr (p, m);
1163 
1164     do_file_mark (panel, current_file, 0);
1165 
1166     return ret;
1167 }
1168 
1169 /* --------------------------------------------------------------------------------------------- */
1170 
1171 static void
1172 chattr_apply_mask (WPanel * panel, vfs_path_t * vpath, unsigned long m)
     /* [previous][next][first][last][top][bottom][index][help]  */
1173 {
1174     gboolean ok;
1175 
1176     if (!do_chattr (panel, vpath, m))
1177         return;
1178 
1179     do
1180     {
1181         const GString *fname;
1182 
1183         fname = next_file (panel);
1184         vpath = vfs_path_from_str (fname->str);
1185         ok = (mc_fgetflags (vpath, &m) == 0);
1186 
1187         if (!ok)
1188         {
1189             /* if current file was deleted outside mc -- try next file */
1190             /* decrease panel->marked */
1191             do_file_mark (panel, current_file, 0);
1192 
1193             /* try next file */
1194             ok = TRUE;
1195         }
1196         else
1197         {
1198             flags = m;
1199             ok = do_chattr (panel, vpath, m);
1200             vfs_path_free (vpath, TRUE);
1201         }
1202     }
1203     while (ok && panel->marked != 0);
1204 }
1205 
1206 /* --------------------------------------------------------------------------------------------- */
1207 /*** public functions ****************************************************************************/
1208 /* --------------------------------------------------------------------------------------------- */
1209 
1210 void
1211 chattr_cmd (WPanel * panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1212 {
1213     gboolean need_update = FALSE;
1214     gboolean end_chattr = FALSE;
1215 
1216     chattr_init ();
1217 
1218     current_file = 0;
1219     ignore_all = FALSE;
1220 
1221     do
1222     {                           /* do while any files remaining */
1223         vfs_path_t *vpath;
1224         WDialog *ch_dlg;
1225         const GString *fname;
1226         size_t i;
1227         int result;
1228 
1229         do_refresh ();
1230 
1231         need_update = FALSE;
1232         end_chattr = FALSE;
1233 
1234         if (panel->marked != 0)
1235             fname = next_file (panel);  /* next marked file */
1236         else
1237             fname = panel_current_entry (panel)->fname; /* single file */
1238 
1239         vpath = vfs_path_from_str (fname->str);
1240 
1241         if (mc_fgetflags (vpath, &flags) != 0)
1242         {
1243             message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str,
1244                      unix_error_string (errno));
1245             vfs_path_free (vpath, TRUE);
1246             break;
1247         }
1248 
1249         flags_changed = FALSE;
1250 
1251         ch_dlg = chattr_dlg_create (panel, fname->str, flags);
1252         result = dlg_run (ch_dlg);
1253         widget_destroy (WIDGET (ch_dlg));
1254 
1255         switch (result)
1256         {
1257         case B_CANCEL:
1258             end_chattr = TRUE;
1259             break;
1260 
1261         case B_ENTER:
1262             if (flags_changed)
1263             {
1264                 if (panel->marked <= 1)
1265                 {
1266                     /* single or last file */
1267                     if (mc_fsetflags (vpath, flags) == -1 && !ignore_all)
1268                         message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str,
1269                                  unix_error_string (errno));
1270                     end_chattr = TRUE;
1271                 }
1272                 else if (!try_chattr (vpath, flags))
1273                 {
1274                     /* stop multiple files processing */
1275                     result = B_CANCEL;
1276                     end_chattr = TRUE;
1277                 }
1278             }
1279 
1280             need_update = TRUE;
1281             break;
1282 
1283         case B_SETALL:
1284         case B_MARKED:
1285             or_mask = 0;
1286             and_mask = ~0;
1287 
1288             for (i = 0; i < check_attr_num; i++)
1289                 if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL))
1290                 {
1291                     if (check_attr[i].state)
1292                         or_mask |= check_attr[i].flags;
1293                     else
1294                         and_mask &= ~check_attr[i].flags;
1295                 }
1296 
1297             chattr_apply_mask (panel, vpath, flags);
1298             need_update = TRUE;
1299             end_chattr = TRUE;
1300             break;
1301 
1302         case B_SETMRK:
1303             or_mask = 0;
1304             and_mask = ~0;
1305 
1306             for (i = 0; i < check_attr_num; i++)
1307                 if (chattr_is_modifiable (i) && check_attr[i].selected)
1308                     or_mask |= check_attr[i].flags;
1309 
1310             chattr_apply_mask (panel, vpath, flags);
1311             need_update = TRUE;
1312             end_chattr = TRUE;
1313             break;
1314 
1315         case B_CLRMRK:
1316             or_mask = 0;
1317             and_mask = ~0;
1318 
1319             for (i = 0; i < check_attr_num; i++)
1320                 if (chattr_is_modifiable (i) && check_attr[i].selected)
1321                     and_mask &= ~check_attr[i].flags;
1322 
1323             chattr_apply_mask (panel, vpath, flags);
1324             need_update = TRUE;
1325             end_chattr = TRUE;
1326             break;
1327 
1328         default:
1329             break;
1330         }
1331 
1332         if (panel->marked != 0 && result != B_CANCEL)
1333         {
1334             do_file_mark (panel, current_file, 0);
1335             need_update = TRUE;
1336         }
1337 
1338         vfs_path_free (vpath, TRUE);
1339 
1340     }
1341     while (panel->marked != 0 && !end_chattr);
1342 
1343     chattr_done (need_update);
1344 }
1345 
1346 /* --------------------------------------------------------------------------------------------- */
1347 
1348 const char *
1349 chattr_get_as_str (unsigned long attr)
     /* [previous][next][first][last][top][bottom][index][help]  */
1350 {
1351     static char str[32 + 1];    /* 32 bits in attributes (unsigned long) */
1352 
1353     chattr_fill_str (attr, str);
1354 
1355     return str;
1356 }
1357 
1358 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */