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

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