/* vifm * Copyright (C) 2012 xaizek. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "attr_dialog_win.h" #include <windows.h> #include <assert.h> #include <stddef.h> /* NULL size_t */ #include <string.h> /* strncat() strlen() */ #include "../../compat/curses.h" #include "../../compat/fs_limits.h" #include "../../engine/keys.h" #include "../../engine/mode.h" #include "../../ui/cancellation.h" #include "../../ui/fileview.h" #include "../../ui/ui.h" #include "../../utils/fs.h" #include "../../utils/log.h" #include "../../utils/macros.h" #include "../../utils/path.h" #include "../../utils/str.h" #include "../../utils/utf8.h" #include "../../utils/utils.h" #include "../../filelist.h" #include "../../flist_sel.h" #include "../../status.h" #include "../../undo.h" #include "../modes.h" #include "../wk.h" #include "msg_dialog.h" /* Enumeration of properties. */ enum { ATTR_ARCHIVE, ATTR_HIDDEN, ATTR_NOT_INDEX, ATTR_READONLY, ATTR_SYSTEM, ATTR_COUNT, PSEUDO_ATTR_RECURSIVE = ATTR_COUNT, ALL_ATTRS, }; static void init(void); static void get_attrs(void); static char * get_title(int max_width); static int is_one_file_selected(int first_file_index); static int get_first_file_index(void); static int get_selection_size(int first_file_index); static void leave_attr_mode(void); static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info); static void cmd_return(key_info_t key_info, keys_info_t *keys_info); static void set_attrs(view_t *view, const int *attrs, const int *origin_attrs); static void files_attrib(view_t *view, DWORD add, DWORD sub, int recurse_dirs); static void file_attrib(char *path, DWORD add, DWORD sub, int recurse_dirs); static void cmd_G(key_info_t key_info, keys_info_t *keys_info); static void cmd_gg(key_info_t key_info, keys_info_t *keys_info); static void cmd_space(key_info_t key_info, keys_info_t *keys_info); static void cmd_j(key_info_t key_info, keys_info_t *keys_info); static void cmd_k(key_info_t key_info, keys_info_t *keys_info); static void inc_curr(void); static void dec_curr(void); static void clear_curr(void); static void draw_curr(void); /* properties dialog width */ static const int WIDTH = 29; static view_t *view; static int top, bottom; static int curr; static int attr_num; static int step; static int col; static int changed; static int file_is_dir; static int attrs[ALL_ATTRS]; static int origin_attrs[ALL_ATTRS]; static const DWORD attr_list[ATTR_COUNT] = { [ATTR_ARCHIVE] = FILE_ATTRIBUTE_ARCHIVE, [ATTR_HIDDEN] = FILE_ATTRIBUTE_HIDDEN, [ATTR_NOT_INDEX] = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, [ATTR_READONLY] = FILE_ATTRIBUTE_READONLY, [ATTR_SYSTEM] = FILE_ATTRIBUTE_SYSTEM, }; ARRAY_GUARD(attr_list, ATTR_COUNT); static const char *attr_strings[ATTR_COUNT] = { [ATTR_ARCHIVE] = "Archive", [ATTR_HIDDEN] = "Hidden", [ATTR_NOT_INDEX] = "No content indexing", [ATTR_READONLY] = "Readonly", [ATTR_SYSTEM] = "System", }; ARRAY_GUARD(attr_strings, ATTR_COUNT); static keys_add_info_t builtin_cmds[] = { {WK_C_c, {{&cmd_ctrl_c}, .descr = "close the dialog"}}, {WK_CR, {{&cmd_return}, .descr = "update permissions"}}, {WK_C_n, {{&cmd_j}, .descr = "go to item below"}}, {WK_C_p, {{&cmd_k}, .descr = "go to item above"}}, {WK_ESC, {{&cmd_ctrl_c}, .descr = "close the dialog"}}, {WK_SPACE, {{&cmd_space}, .descr = "toggle current item"}}, {WK_G, {{&cmd_G}, .descr = "go to the last item"}}, {WK_Z WK_Q, {{&cmd_ctrl_c}, .descr = "close the dialog"}}, {WK_Z WK_Z, {{&cmd_ctrl_c}, .descr = "close the dialog"}}, {WK_g WK_g, {{&cmd_gg}, .descr = "go to the first item"}}, {WK_h, {{&cmd_space}, .descr = "toggle current item"}}, {WK_j, {{&cmd_j}, .descr = "go to item below"}}, {WK_k, {{&cmd_k}, .descr = "go to item above"}}, {WK_l, {{&cmd_return}, .descr = "update permissions"}}, {WK_q, {{&cmd_ctrl_c}, .descr = "close the dialog"}}, {WK_t, {{&cmd_space}, .descr = "toggle current item"}}, #ifdef ENABLE_EXTENDED_KEYS {{K(KEY_HOME)}, {{&cmd_gg}, .descr = "go to the first item"}}, {{K(KEY_END)}, {{&cmd_G}, .descr = "go to the last item"}}, {{K(KEY_UP)}, {{&cmd_k}, .descr = "go to item above"}}, {{K(KEY_DOWN)}, {{&cmd_j}, .descr = "go to item below"}}, {{K(KEY_RIGHT)}, {{&cmd_return}, .descr = "update permissions"}}, #endif /* ENABLE_EXTENDED_KEYS */ }; /* initializes properties change mode */ void init_attr_dialog_mode(void) { int ret_code; ret_code = vle_keys_add(builtin_cmds, ARRAY_LEN(builtin_cmds), ATTR_MODE); assert(ret_code == 0); (void)ret_code; } /* enters properties change mode */ void enter_attr_mode(view_t *active_view) { flist_set_marking(active_view, 0); if(curr_stats.load_stage < 2) return; ui_hide_graphics(); view = active_view; vle_mode_set(ATTR_MODE, VMT_SECONDARY); modes_input_bar_clear(); curr_stats.use_input_bar = 0; init(); get_attrs(); redraw_attr_dialog(); } /* initializes inner state of the unit */ static void init(void) { top = 2; bottom = file_is_dir ? 8 : 6; curr = 2; attr_num = 0; step = 1; col = 2; changed = 0; } /* initializes file attributes array */ static void get_attrs(void) { int i; DWORD attributes; DWORD diff = 0; dir_entry_t *entry = NULL; int first = 1; file_is_dir = 0; while(iter_marked_entries(view, &entry)) { if(first) { attributes = entry->attrs; first = 0; } diff |= (entry->attrs ^ attributes); file_is_dir |= fentry_is_dir(entry); } if(first) { show_error_msg("Attributes", "No files to process"); return; } /* FIXME: allow setting attributes recursively. */ file_is_dir = 0; memset(attrs, 0, sizeof(attrs)); for(i = 0; i < ATTR_COUNT; ++i) { attrs[i] = !(diff & attr_list[i]) ? (int)(attributes & attr_list[i]) : -1; } memcpy(origin_attrs, attrs, sizeof(attrs)); } /* Composes title for the dialog. Returns pointer to a newly allocated * string. */ static char * get_title(int max_width) { const int first_file_index = get_first_file_index(); if(!is_one_file_selected(first_file_index)) { return format_str(" %d files ", get_selection_size(first_file_index)); } char *escaped = escape_unreadable(view->dir_entry[first_file_index].name); char *ellipsed = right_ellipsis(escaped, max_width - 2, curr_stats.ellipsis); free(escaped); char *title = format_str(" %s ", ellipsed); free(ellipsed); return title; } /* Checks whether single file is selected. Returns non-zero if so, otherwise * zero is returned. */ static int is_one_file_selected(int first_file_index) { int i; if(!view->dir_entry[first_file_index].selected) { return 1; } i = first_file_index + 1; while(i < view->list_rows && !view->dir_entry[i].selected) { i++; } return i >= view->list_rows; } /* Gets index of the first one on which chmod operation applies. Returns index * of the first subjected file. */ static int get_first_file_index(void) { int i = 0; while(i < view->list_rows && !view->dir_entry[i].selected) { i++; } return (i == view->list_rows) ? view->list_pos : i; } /* Gets number of files, which will be affected by the chmod operation. */ static int get_selection_size(int first_file_index) { int selection_size = 1; int i = first_file_index + 1; while(i < view->list_rows) { if(view->dir_entry[i].selected) { selection_size++; } i++; } return selection_size; } void redraw_attr_dialog(void) { char *title; int i; int x, y; werase(change_win); if(file_is_dir) wresize(change_win, 11, WIDTH); else wresize(change_win, 9, WIDTH); y = 2; x = 2; for(i = 0; i < ATTR_COUNT; i++) { mvwaddstr(change_win, y, x, " [ ] "); mvwaddstr(change_win, y, x + 5, attr_strings[i]); if(attrs[i]) mvwaddch(change_win, y, x + 2, (attrs[i] < 0) ? 'X' : '*'); y += 1; } if(file_is_dir) mvwaddstr(change_win, y + 1, x, " [ ] Set Recursively"); getmaxyx(stdscr, y, x); mvwin(change_win, (y - getmaxy(change_win))/2, (x - getmaxx(change_win))/2); box(change_win, 0, 0); title = get_title(getmaxx(change_win) - 2); mvwaddstr(change_win, 0, (getmaxx(change_win) - utf8_strsw(title))/2, title); free(title); draw_curr(); } /* leaves properties change dialog */ static void leave_attr_mode(void) { vle_mode_set(NORMAL_MODE, VMT_PRIMARY); curr_stats.use_input_bar = 1; flist_sel_stash(view); ui_view_schedule_reload(view); } /* leaves properties change dialog */ static void cmd_ctrl_c(key_info_t key_info, keys_info_t *keys_info) { leave_attr_mode(); } /* leaves properties change dialog after changing file attributes */ static void cmd_return(key_info_t key_info, keys_info_t *keys_info) { if(changed) { set_attrs(view, attrs, origin_attrs); leave_attr_mode(); } } /* sets file properties according to users input. forms attribute change mask */ static void set_attrs(view_t *view, const int *attrs, const int *origin_attrs) { int i; DWORD add_mask = 0; DWORD sub_mask = 0; for(i = 0; i < ALL_ATTRS; i++) { if(attrs[i] == -1 || attrs[i] == origin_attrs[i]) continue; if(attrs[i]) { add_mask |= attr_list[i]; } else { sub_mask |= attr_list[i]; } } files_attrib(view, add_mask, sub_mask, attrs[PSEUDO_ATTR_RECURSIVE]); } /* Changes attributes of files in the view. */ static void files_attrib(view_t *view, DWORD add, DWORD sub, int recurse_dirs) { char undo_msg[COMMAND_GROUP_INFO_LEN]; dir_entry_t *entry; size_t len; snprintf(undo_msg, sizeof(undo_msg), "chmod in %s: ", replace_home_part(flist_get_dir(view))); len = strlen(undo_msg); ui_cancellation_push_off(); entry = NULL; while(iter_marked_entries(view, &entry)) { if(len >= 2U && undo_msg[len - 2U] != ':') { strncat(undo_msg + len, ", ", sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } strncat(undo_msg + len, entry->name, sizeof(undo_msg) - len - 1); len += strlen(undo_msg + len); } un_group_open(undo_msg); entry = NULL; while(iter_marked_entries(view, &entry) && !ui_cancellation_requested()) { char path[PATH_MAX + 1]; get_full_path_of(entry, sizeof(path), path); file_attrib(path, add, sub, recurse_dirs); } un_group_close(); ui_cancellation_pop(); } /* performs properties change with support of undoing */ static void file_attrib(char *path, DWORD add, DWORD sub, int recurse_dirs) { /* FIXME: set attributes recursively. */ DWORD attrs = GetFileAttributes(path); if(attrs == INVALID_FILE_ATTRIBUTES) { LOG_WERROR(GetLastError()); return; } if(add != 0) { const size_t wadd = add; if(perform_operation(OP_ADDATTR, NULL, (void *)wadd, path, NULL) == OPS_SUCCEEDED) { un_group_add_op(OP_ADDATTR, (void *)wadd, (void *)(~attrs & wadd), path, ""); } } if(sub != 0) { const size_t wsub = sub; if(perform_operation(OP_SUBATTR, NULL, (void *)wsub, path, NULL) == OPS_SUCCEEDED) { un_group_add_op(OP_SUBATTR, (void *)wsub, (void *)(~attrs & wsub), path, ""); } } } /* goes to the bottom of the properties dialog */ static void cmd_G(key_info_t key_info, keys_info_t *keys_info) { clear_curr(); while(curr < bottom) { inc_curr(); attr_num++; } draw_curr(); } /* goes to the top of the properties dialog */ static void cmd_gg(key_info_t key_info, keys_info_t *keys_info) { clear_curr(); while(curr > top) { dec_curr(); attr_num--; } draw_curr(); } /* changes selected flag's state */ static void cmd_space(key_info_t key_info, keys_info_t *keys_info) { char c; changed = 1; if(attrs[attr_num] < 0) { c = ' '; attrs[attr_num] = 0; } else if(curr == 7) { attrs[attr_num] = !attrs[attr_num]; c = attrs[attr_num] ? '*' : ' '; } else if(origin_attrs[attr_num] < 0) { if(attrs[attr_num] > 0) { c = 'X'; attrs[attr_num] = -1; } else if(attrs[attr_num] == 0) { /* attrs[attr_num] < 0 is handled above, so it must be 0 here. */ c = '*'; attrs[attr_num] = 1; } } else { c = attrs[attr_num] ? ' ' : '*'; attrs[attr_num] = !attrs[attr_num]; } mvwaddch(change_win, curr, 4, c); ui_refresh_win(change_win); } /* moves cursor down one or more times */ static void cmd_j(key_info_t key_info, keys_info_t *keys_info) { clear_curr(); inc_curr(); attr_num++; if(curr > bottom) { dec_curr(); attr_num--; } draw_curr(); } /* moves cursor up one or more times */ static void cmd_k(key_info_t key_info, keys_info_t *keys_info) { clear_curr(); dec_curr(); attr_num--; if(curr < top) { inc_curr(); attr_num++; } draw_curr(); } /* moves cursor one line up */ static void inc_curr(void) { curr += step; if(curr == 7) curr++; } /* moves cursor one down up */ static void dec_curr(void) { curr -= step; if(curr == 7) curr--; } /* clears cursor marker */ static void clear_curr(void) { mvwaddch(change_win, curr, col, ' '); } /* draws cursor marker */ static void draw_curr(void) { mvwaddch(change_win, curr, col, '>'); ui_refresh_win(change_win); checked_wmove(change_win, curr, col); } /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set cinoptions+=t0 filetype=c : */