summaryrefslogtreecommitdiffstats
path: root/src/filemanager
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager')
-rw-r--r--src/filemanager/Makefile.am40
-rw-r--r--src/filemanager/Makefile.in839
-rw-r--r--src/filemanager/achown.c1107
-rw-r--r--src/filemanager/boxes.c1343
-rw-r--r--src/filemanager/boxes.h37
-rw-r--r--src/filemanager/cd.c310
-rw-r--r--src/filemanager/cd.h23
-rw-r--r--src/filemanager/chattr.c1358
-rw-r--r--src/filemanager/chmod.c664
-rw-r--r--src/filemanager/chown.c552
-rw-r--r--src/filemanager/cmd.c1470
-rw-r--r--src/filemanager/cmd.h172
-rw-r--r--src/filemanager/command.c255
-rw-r--r--src/filemanager/command.h27
-rw-r--r--src/filemanager/dir.c839
-rw-r--r--src/filemanager/dir.h115
-rw-r--r--src/filemanager/ext.c1089
-rw-r--r--src/filemanager/ext.h33
-rw-r--r--src/filemanager/file.c3562
-rw-r--r--src/filemanager/file.h72
-rw-r--r--src/filemanager/filegui.c1498
-rw-r--r--src/filemanager/filegui.h40
-rw-r--r--src/filemanager/filemanager.c1852
-rw-r--r--src/filemanager/filemanager.h53
-rw-r--r--src/filemanager/filenot.c150
-rw-r--r--src/filemanager/filenot.h26
-rw-r--r--src/filemanager/fileopctx.c128
-rw-r--r--src/filemanager/fileopctx.h198
-rw-r--r--src/filemanager/find.c1968
-rw-r--r--src/filemanager/hotlist.c1733
-rw-r--r--src/filemanager/hotlist.h33
-rw-r--r--src/filemanager/info.c377
-rw-r--r--src/filemanager/info.h24
-rw-r--r--src/filemanager/ioblksize.h86
-rw-r--r--src/filemanager/layout.c1580
-rw-r--r--src/filemanager/layout.h98
-rw-r--r--src/filemanager/mountlist.c1575
-rw-r--r--src/filemanager/mountlist.h44
-rw-r--r--src/filemanager/panel.c5428
-rw-r--r--src/filemanager/panel.h285
-rw-r--r--src/filemanager/panelize.c573
-rw-r--r--src/filemanager/panelize.h25
-rw-r--r--src/filemanager/tree.c1345
-rw-r--r--src/filemanager/tree.h35
-rw-r--r--src/filemanager/treestore.c941
-rw-r--r--src/filemanager/treestore.h63
46 files changed, 34065 insertions, 0 deletions
diff --git a/src/filemanager/Makefile.am b/src/filemanager/Makefile.am
new file mode 100644
index 0000000..534d8dc
--- /dev/null
+++ b/src/filemanager/Makefile.am
@@ -0,0 +1,40 @@
+
+noinst_LTLIBRARIES = libmcfilemanager.la
+
+libmcfilemanager_la_SOURCES = \
+ achown.c \
+ boxes.c boxes.h \
+ cd.c cd.h \
+ chmod.c \
+ chown.c \
+ cmd.c cmd.h \
+ command.c command.h \
+ dir.c dir.h \
+ ext.c ext.h \
+ file.c file.h \
+ filegui.c filegui.h \
+ filemanager.h filemanager.c \
+ filenot.c filenot.h \
+ fileopctx.c fileopctx.h \
+ find.c \
+ hotlist.c hotlist.h \
+ info.c info.h \
+ ioblksize.h \
+ layout.c layout.h \
+ mountlist.c mountlist.h \
+ panelize.c panelize.h \
+ panel.c panel.h \
+ tree.c tree.h \
+ treestore.c treestore.h
+
+# Unmaintained, unsupported, etc
+# listmode.c listmode.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+
+if ENABLE_EXT2FS_ATTR
+libmcfilemanager_la_SOURCES += \
+ chattr.c
+
+AM_CPPFLAGS += @EXT2FS_CFLAGS@ @E2P_CFLAGS@
+endif
diff --git a/src/filemanager/Makefile.in b/src/filemanager/Makefile.in
new file mode 100644
index 0000000..2e1300b
--- /dev/null
+++ b/src/filemanager/Makefile.in
@@ -0,0 +1,839 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@ENABLE_EXT2FS_ATTR_TRUE@am__append_1 = \
+@ENABLE_EXT2FS_ATTR_TRUE@ chattr.c
+
+@ENABLE_EXT2FS_ATTR_TRUE@am__append_2 = @EXT2FS_CFLAGS@ @E2P_CFLAGS@
+subdir = src/filemanager
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/gettext.m4 \
+ $(top_srcdir)/m4/iconv.m4 $(top_srcdir)/m4/intlmacosx.m4 \
+ $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \
+ $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/nls.m4 \
+ $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+ $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4.include/gnulib/mode_t.m4 \
+ $(top_srcdir)/m4.include/gnulib/stat-size.m4 \
+ $(top_srcdir)/m4.include/gnulib/fstypename.m4 \
+ $(top_srcdir)/m4.include/gnulib/fsusage.m4 \
+ $(top_srcdir)/m4.include/gnulib/mountlist.m4 \
+ $(top_srcdir)/m4.include/gnulib/windows-stat-inodes.m4 \
+ $(top_srcdir)/m4.include/gnulib/sys_types_h.m4 \
+ $(top_srcdir)/m4.include/ax_path_lib_pcre.m4 \
+ $(top_srcdir)/m4.include/ax_check_pcre2.m4 \
+ $(top_srcdir)/m4.include/dx_doxygen.m4 \
+ $(top_srcdir)/m4.include/ax_require_defined.m4 \
+ $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_flag.m4 \
+ $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \
+ $(top_srcdir)/m4.include/mc-cflags.m4 \
+ $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4.include/mc-check-search-type.m4 \
+ $(top_srcdir)/m4.include/mc-get-fs-info.m4 \
+ $(top_srcdir)/m4.include/mc-with-x.m4 \
+ $(top_srcdir)/m4.include/mc-use-termcap.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \
+ $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \
+ $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \
+ $(top_srcdir)/m4.include/mc-subshell.m4 \
+ $(top_srcdir)/m4.include/mc-background.m4 \
+ $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \
+ $(top_srcdir)/m4.include/mc-glib.m4 \
+ $(top_srcdir)/m4.include/mc-vfs.m4 \
+ $(top_srcdir)/m4.include/vfs/rpc.m4 \
+ $(top_srcdir)/m4.include/vfs/socket.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \
+ $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \
+ $(top_srcdir)/m4.include/mc-version.m4 \
+ $(top_srcdir)/m4.include/mc-tests.m4 \
+ $(top_srcdir)/m4.include/mc-i18n.m4 \
+ $(top_srcdir)/m4.include/mc-assert.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libmcfilemanager_la_LIBADD =
+am__libmcfilemanager_la_SOURCES_DIST = achown.c boxes.c boxes.h cd.c \
+ cd.h chmod.c chown.c cmd.c cmd.h command.c command.h dir.c \
+ dir.h ext.c ext.h file.c file.h filegui.c filegui.h \
+ filemanager.h filemanager.c filenot.c filenot.h fileopctx.c \
+ fileopctx.h find.c hotlist.c hotlist.h info.c info.h \
+ ioblksize.h layout.c layout.h mountlist.c mountlist.h \
+ panelize.c panelize.h panel.c panel.h tree.c tree.h \
+ treestore.c treestore.h chattr.c
+@ENABLE_EXT2FS_ATTR_TRUE@am__objects_1 = chattr.lo
+am_libmcfilemanager_la_OBJECTS = achown.lo boxes.lo cd.lo chmod.lo \
+ chown.lo cmd.lo command.lo dir.lo ext.lo file.lo filegui.lo \
+ filemanager.lo filenot.lo fileopctx.lo find.lo hotlist.lo \
+ info.lo layout.lo mountlist.lo panelize.lo panel.lo tree.lo \
+ treestore.lo $(am__objects_1)
+libmcfilemanager_la_OBJECTS = $(am_libmcfilemanager_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/config/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/achown.Plo ./$(DEPDIR)/boxes.Plo \
+ ./$(DEPDIR)/cd.Plo ./$(DEPDIR)/chattr.Plo \
+ ./$(DEPDIR)/chmod.Plo ./$(DEPDIR)/chown.Plo \
+ ./$(DEPDIR)/cmd.Plo ./$(DEPDIR)/command.Plo \
+ ./$(DEPDIR)/dir.Plo ./$(DEPDIR)/ext.Plo ./$(DEPDIR)/file.Plo \
+ ./$(DEPDIR)/filegui.Plo ./$(DEPDIR)/filemanager.Plo \
+ ./$(DEPDIR)/filenot.Plo ./$(DEPDIR)/fileopctx.Plo \
+ ./$(DEPDIR)/find.Plo ./$(DEPDIR)/hotlist.Plo \
+ ./$(DEPDIR)/info.Plo ./$(DEPDIR)/layout.Plo \
+ ./$(DEPDIR)/mountlist.Plo ./$(DEPDIR)/panel.Plo \
+ ./$(DEPDIR)/panelize.Plo ./$(DEPDIR)/tree.Plo \
+ ./$(DEPDIR)/treestore.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libmcfilemanager_la_SOURCES)
+DIST_SOURCES = $(am__libmcfilemanager_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/config/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHECK_CFLAGS = @CHECK_CFLAGS@
+CHECK_LIBS = @CHECK_LIBS@
+COM_ERR_CFLAGS = @COM_ERR_CFLAGS@
+COM_ERR_LIBS = @COM_ERR_LIBS@
+CP1251 = @CP1251@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOC_LINGUAS = @DOC_LINGUAS@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+E2P_CFLAGS = @E2P_CFLAGS@
+E2P_LIBS = @E2P_LIBS@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+EXT2FS_CFLAGS = @EXT2FS_CFLAGS@
+EXT2FS_LIBS = @EXT2FS_LIBS@
+EXTHELPERSDIR = @EXTHELPERSDIR@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GMODULE_CFLAGS = @GMODULE_CFLAGS@
+GMODULE_LIBS = @GMODULE_LIBS@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+HAVE_FILECMD = @HAVE_FILECMD@
+HAVE_ZIPINFO = @HAVE_ZIPINFO@
+HAVE_nroff = @HAVE_nroff@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBMC_RELEASE = @LIBMC_RELEASE@
+LIBMC_VERSION = @LIBMC_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSSH_CFLAGS = @LIBSSH_CFLAGS@
+LIBSSH_LIBS = @LIBSSH_LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANDOC = @MANDOC@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAN_DATE = @MAN_DATE@
+MAN_FLAGS = @MAN_FLAGS@
+MAN_VERSION = @MAN_VERSION@
+MCLIBS = @MCLIBS@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CFLAGS = @PCRE_CFLAGS@
+PCRE_LIBS = @PCRE_LIBS@
+PERL = @PERL@
+PERL_FOR_BUILD = @PERL_FOR_BUILD@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSUB = @POSUB@
+PYTHON = @PYTHON@
+RANLIB = @RANLIB@
+RUBY = @RUBY@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SLANG_CFLAGS = @SLANG_CFLAGS@
+SLANG_LIBS = @SLANG_LIBS@
+STRIP = @STRIP@
+TESTS_LDFLAGS = @TESTS_LDFLAGS@
+UNZIP = @UNZIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+X11_WWW = @X11_WWW@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+XMKMF = @XMKMF@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+ZIP = @ZIP@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libmcfilemanager.la
+libmcfilemanager_la_SOURCES = achown.c boxes.c boxes.h cd.c cd.h \
+ chmod.c chown.c cmd.c cmd.h command.c command.h dir.c dir.h \
+ ext.c ext.h file.c file.h filegui.c filegui.h filemanager.h \
+ filemanager.c filenot.c filenot.h fileopctx.c fileopctx.h \
+ find.c hotlist.c hotlist.h info.c info.h ioblksize.h layout.c \
+ layout.h mountlist.c mountlist.h panelize.c panelize.h panel.c \
+ panel.h tree.c tree.h treestore.c treestore.h $(am__append_1)
+
+# Unmaintained, unsupported, etc
+# listmode.c listmode.h
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS) $(am__append_2)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/filemanager/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu src/filemanager/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libmcfilemanager.la: $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_DEPENDENCIES) $(EXTRA_libmcfilemanager_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcfilemanager_la_OBJECTS) $(libmcfilemanager_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/achown.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxes.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chattr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chmod.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/chown.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cmd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ext.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filegui.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filemanager.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filenot.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileopctx.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/find.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hotlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/layout.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mountlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panel.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/panelize.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tree.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/treestore.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/achown.Plo
+ -rm -f ./$(DEPDIR)/boxes.Plo
+ -rm -f ./$(DEPDIR)/cd.Plo
+ -rm -f ./$(DEPDIR)/chattr.Plo
+ -rm -f ./$(DEPDIR)/chmod.Plo
+ -rm -f ./$(DEPDIR)/chown.Plo
+ -rm -f ./$(DEPDIR)/cmd.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/ext.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/filegui.Plo
+ -rm -f ./$(DEPDIR)/filemanager.Plo
+ -rm -f ./$(DEPDIR)/filenot.Plo
+ -rm -f ./$(DEPDIR)/fileopctx.Plo
+ -rm -f ./$(DEPDIR)/find.Plo
+ -rm -f ./$(DEPDIR)/hotlist.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/layout.Plo
+ -rm -f ./$(DEPDIR)/mountlist.Plo
+ -rm -f ./$(DEPDIR)/panel.Plo
+ -rm -f ./$(DEPDIR)/panelize.Plo
+ -rm -f ./$(DEPDIR)/tree.Plo
+ -rm -f ./$(DEPDIR)/treestore.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/achown.Plo
+ -rm -f ./$(DEPDIR)/boxes.Plo
+ -rm -f ./$(DEPDIR)/cd.Plo
+ -rm -f ./$(DEPDIR)/chattr.Plo
+ -rm -f ./$(DEPDIR)/chmod.Plo
+ -rm -f ./$(DEPDIR)/chown.Plo
+ -rm -f ./$(DEPDIR)/cmd.Plo
+ -rm -f ./$(DEPDIR)/command.Plo
+ -rm -f ./$(DEPDIR)/dir.Plo
+ -rm -f ./$(DEPDIR)/ext.Plo
+ -rm -f ./$(DEPDIR)/file.Plo
+ -rm -f ./$(DEPDIR)/filegui.Plo
+ -rm -f ./$(DEPDIR)/filemanager.Plo
+ -rm -f ./$(DEPDIR)/filenot.Plo
+ -rm -f ./$(DEPDIR)/fileopctx.Plo
+ -rm -f ./$(DEPDIR)/find.Plo
+ -rm -f ./$(DEPDIR)/hotlist.Plo
+ -rm -f ./$(DEPDIR)/info.Plo
+ -rm -f ./$(DEPDIR)/layout.Plo
+ -rm -f ./$(DEPDIR)/mountlist.Plo
+ -rm -f ./$(DEPDIR)/panel.Plo
+ -rm -f ./$(DEPDIR)/panelize.Plo
+ -rm -f ./$(DEPDIR)/tree.Plo
+ -rm -f ./$(DEPDIR)/treestore.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/filemanager/achown.c b/src/filemanager/achown.c
new file mode 100644
index 0000000..dca3eca
--- /dev/null
+++ b/src/filemanager/achown.c
@@ -0,0 +1,1107 @@
+/*
+ Chown-advanced command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file achown.c
+ * \brief Source: Contains functions for advanced chowning
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "cmd.h" /* advanced_chown_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define BX 5
+#define BY 5
+
+#define BUTTONS 9
+#define BUTTONS_PERM 5
+
+#define B_SETALL B_USER
+#define B_SKIP (B_USER + 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ unsigned long id;
+ int ret_cmd;
+ button_flags_t flags;
+ int x;
+ int len;
+ const char *text;
+} advanced_chown_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { 0, B_ENTER, NARROW_BUTTON, 3, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 11, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 19, 0, " " },
+ { 0, B_ENTER, NARROW_BUTTON, 29, 0, "" },
+ { 0, B_ENTER, NARROW_BUTTON, 47, 0, "" },
+
+ { 0, B_SETALL, NORMAL_BUTTON, 0, 0, N_("Set &all") },
+ { 0, B_SKIP, NORMAL_BUTTON, 0, 0, N_("S&kip") },
+ { 0, B_ENTER, DEFPUSH_BUTTON, 0, 0, N_("&Set") },
+ { 0, B_CANCEL, NORMAL_BUTTON, 0, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+static int current_file;
+static gboolean ignore_all;
+
+static WButton *b_att[3]; /* permission */
+static WButton *b_user, *b_group; /* owner */
+static WLabel *l_filename;
+static WLabel *l_mode;
+
+static int flag_pos;
+static int x_toggle;
+static char ch_flags[11];
+static const char ch_perm[] = "rwx";
+static mode_t ch_cmode;
+static struct stat sf_stat;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+ for (i = BUTTONS_PERM; i < BUTTONS; i++)
+ {
+#ifdef ENABLE_NLS
+ advanced_chown_but[i].text = _(advanced_chown_but[i].text);
+#endif /* ENABLE_NLS */
+
+ advanced_chown_but[i].len = str_term_width1 (advanced_chown_but[i].text) + 3;
+ if (advanced_chown_but[i].flags == DEFPUSH_BUTTON)
+ advanced_chown_but[i].len += 2; /* "<>" */
+ }
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+inc_flag_pos (void)
+{
+ if (flag_pos == 10)
+ {
+ flag_pos = 0;
+ return MSG_NOT_HANDLED;
+ }
+
+ flag_pos++;
+
+ return flag_pos % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dec_flag_pos (void)
+{
+ if (flag_pos == 0)
+ {
+ flag_pos = 10;
+ return MSG_NOT_HANDLED;
+ }
+
+ flag_pos--;
+
+ return (flag_pos + 1) % 3 == 0 ? MSG_NOT_HANDLED : MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_perm_by_flags (char *s, int f_p)
+{
+ int i;
+
+ for (i = 0; i < 3; i++)
+ {
+ if (ch_flags[f_p + i] == '+')
+ s[i] = ch_perm[i];
+ else if (ch_flags[f_p + i] == '-')
+ s[i] = '-';
+ else
+ s[i] = (ch_cmode & (1 << (8 - f_p - i))) != 0 ? ch_perm[i] : '-';
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mode_t
+get_perm (char *s, int base)
+{
+ mode_t m = 0;
+
+ m |= (s[0] == '-') ? 0 :
+ ((s[0] == '+') ? (mode_t) (1 << (base + 2)) : (1 << (base + 2)) & ch_cmode);
+
+ m |= (s[1] == '-') ? 0 :
+ ((s[1] == '+') ? (mode_t) (1 << (base + 1)) : (1 << (base + 1)) & ch_cmode);
+
+ m |= (s[2] == '-') ? 0 : ((s[2] == '+') ? (mode_t) (1 << base) : (1 << base) & ch_cmode);
+
+ return m;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mode_t
+get_mode (void)
+{
+ mode_t m;
+
+ m = ch_cmode ^ (ch_cmode & 0777);
+ m |= get_perm (ch_flags, 6);
+ m |= get_perm (ch_flags + 3, 3);
+ m |= get_perm (ch_flags + 6, 0);
+
+ return m;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_permissions (void)
+{
+ set_perm_by_flags (b_att[0]->text.start, 0);
+ set_perm_by_flags (b_att[1]->text.start, 3);
+ set_perm_by_flags (b_att[2]->text.start, 6);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_ownership (void)
+{
+ button_set_text (b_user, get_owner (sf_stat.st_uid));
+ button_set_text (b_group, get_group (sf_stat.st_gid));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+print_flags (const WDialog * h)
+{
+ int i;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[0].x + 6 + i);
+ tty_print_char (ch_flags[i]);
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[1].x + 6 + i);
+ tty_print_char (ch_flags[i + 3]);
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[2].x + 6 + i);
+ tty_print_char (ch_flags[i + 6]);
+ }
+
+ update_permissions ();
+
+ for (i = 0; i < 15; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[3].x + 6 + i);
+ tty_print_char (ch_flags[9]);
+ }
+ for (i = 0; i < 15; i++)
+ {
+ widget_gotoyx (h, BY + 1, advanced_chown_but[4].x + 6 + i);
+ tty_print_char (ch_flags[10]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_refresh (const WDialog * h)
+{
+ tty_setcolor (COLOR_NORMAL);
+
+ widget_gotoyx (h, BY - 1, advanced_chown_but[0].x + 5);
+ tty_print_string (_("owner"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[1].x + 5);
+ tty_print_string (_("group"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[2].x + 5);
+ tty_print_string (_("other"));
+
+ widget_gotoyx (h, BY - 1, advanced_chown_but[3].x + 5);
+ tty_print_string (_("owner"));
+ widget_gotoyx (h, BY - 1, advanced_chown_but[4].x + 5);
+ tty_print_string (_("group"));
+
+ widget_gotoyx (h, BY + 1, 3);
+ tty_print_string (_("Flag"));
+ print_flags (h);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_info_update (void)
+{
+ /* mode */
+ label_set_textv (l_mode, _("Permissions (octal): %o"), get_mode ());
+
+ /* permissions */
+ update_permissions ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_mode (WGroup * g)
+{
+ print_flags (DIALOG (g));
+ advanced_chown_info_update ();
+ widget_set_state (WIDGET (g->current->data), WST_FOCUSED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+perm_button_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButton *b = BUTTON (w);
+ WGroup *g = w->owner;
+ int i = 0;
+ int f_pos;
+
+ /* one of permission buttons */
+ if (b == b_att[0])
+ f_pos = 0;
+ else if (b == b_att[1])
+ f_pos = 1;
+ else /* if (w == b_att [1] */
+ f_pos = 2;
+
+ switch (msg)
+ {
+ case MSG_FOCUS:
+ if (b->hotpos == -1)
+ b->hotpos = 0;
+
+ flag_pos = f_pos * 3 + b->hotpos;
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ switch (parm)
+ {
+ case '*':
+ parm = '=';
+ MC_FALLTHROUGH;
+
+ case '-':
+ case '=':
+ case '+':
+ flag_pos = f_pos * 3 + b->hotpos;
+ ch_flags[flag_pos] = parm;
+ update_mode (g);
+ send_message (w, NULL, MSG_KEY, KEY_RIGHT, NULL);
+ if (b->hotpos == 2)
+ group_select_next_widget (g);
+ break;
+
+ case XCTRL ('f'):
+ case KEY_RIGHT:
+ {
+ cb_ret_t ret;
+
+ ret = inc_flag_pos ();
+ b->hotpos = flag_pos % 3;
+ return ret;
+ }
+
+ case XCTRL ('b'):
+ case KEY_LEFT:
+ {
+ cb_ret_t ret;
+
+ ret = dec_flag_pos ();
+ b->hotpos = flag_pos % 3;
+ return ret;
+ }
+
+ case 'x':
+ i++;
+ MC_FALLTHROUGH;
+
+ case 'w':
+ i++;
+ MC_FALLTHROUGH;
+
+ case 'r':
+ b->hotpos = i;
+ MC_FALLTHROUGH;
+
+ case ' ':
+ i = b->hotpos;
+
+ flag_pos = f_pos * 3 + i;
+ if (b->text.start[flag_pos % 3] == '-')
+ ch_flags[flag_pos] = '+';
+ else
+ ch_flags[flag_pos] = '-';
+ update_mode (w->owner);
+ break;
+
+ case '4':
+ i++;
+ MC_FALLTHROUGH;
+
+ case '2':
+ i++;
+ MC_FALLTHROUGH;
+
+ case '1':
+ b->hotpos = i;
+ flag_pos = f_pos * 3 + i;
+ ch_flags[flag_pos] = '=';
+ update_mode (g);
+ break;
+
+ default:
+ break;
+ }
+ /* continue key handling in the dialog level */
+ return MSG_NOT_HANDLED;
+
+ default:
+ return button_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+perm_button_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ /* place cursor on flag that is being modified */
+ BUTTON (w)->hotpos = CLAMP (event->x - 1, 0, 2);
+ MC_FALLTHROUGH;
+
+ default:
+ button_mouse_default_callback (w, msg, event);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WButton *
+perm_button_new (int y, int x, int action, button_flags_t flags, const char *text,
+ bcback_fn callback)
+{
+ WButton *b;
+ Widget *w;
+
+ /* create base button using native API */
+ b = button_new (y, x, action, flags, text, callback);
+ w = WIDGET (b);
+
+ /* we don't want HOTKEY */
+ widget_want_hotkey (w, FALSE);
+
+ w->callback = perm_button_callback;
+ w->mouse_callback = perm_button_mouse_callback;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chl_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ {
+ WDialog *h = DIALOG (w);
+
+ h->ret_value = parm;
+ dlg_close (h);
+ }
+ break;
+ default:
+ break;
+ }
+ MC_FALLTHROUGH;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+user_group_button_cb (WButton * button, int action)
+{
+ Widget *w = WIDGET (button);
+ int f_pos;
+ gboolean chl_end;
+
+ (void) action;
+
+ if (button == b_user)
+ f_pos = BUTTONS_PERM - 2;
+ else if (button == b_group)
+ f_pos = BUTTONS_PERM - 1;
+ else
+ return 0; /* do nothing */
+
+ do
+ {
+ WGroup *g = w->owner;
+ WDialog *h = DIALOG (g);
+ Widget *wh = WIDGET (h);
+
+ gboolean is_owner = (f_pos == BUTTONS_PERM - 2);
+ const char *title;
+ int lxx, b_current;
+ WDialog *chl_dlg;
+ WListbox *chl_list;
+ int result;
+ int fe;
+ struct passwd *chl_pass;
+ struct group *chl_grp;
+
+ chl_end = FALSE;
+
+ if (is_owner)
+ {
+ title = _("owner");
+ lxx = WIDGET (b_user)->rect.x + 1;
+ }
+ else
+ {
+ title = _("group");
+ lxx = WIDGET (b_group)->rect.x + 1;
+ }
+
+ chl_dlg =
+ dlg_create (TRUE, wh->rect.y - 1, lxx, wh->rect.lines + 2, 17, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, chl_callback, NULL, "[Advanced Chown]", title);
+
+ /* get new listboxes */
+ chl_list =
+ listbox_new (1, 1, WIDGET (chl_dlg)->rect.lines - 2, WIDGET (chl_dlg)->rect.cols - 2,
+ FALSE, NULL);
+ listbox_add_item (chl_list, LISTBOX_APPEND_AT_END, 0, "<Unknown>", NULL, FALSE);
+ if (is_owner)
+ {
+ /* get and put user names in the listbox */
+ setpwent ();
+ while ((chl_pass = getpwent ()) != NULL)
+ listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_pass->pw_name, NULL,
+ FALSE);
+ endpwent ();
+ fe = listbox_search_text (chl_list, get_owner (sf_stat.st_uid));
+ }
+ else
+ {
+ /* get and put group names in the listbox */
+ setgrent ();
+ while ((chl_grp = getgrent ()) != NULL)
+ listbox_add_item (chl_list, LISTBOX_APPEND_SORTED, 0, chl_grp->gr_name, NULL,
+ FALSE);
+ endgrent ();
+ fe = listbox_search_text (chl_list, get_group (sf_stat.st_gid));
+ }
+
+ listbox_set_current (chl_list, fe);
+
+ b_current = chl_list->current;
+ group_add_widget (GROUP (chl_dlg), chl_list);
+
+ result = dlg_run (chl_dlg);
+
+ if (result != B_CANCEL)
+ {
+ if (b_current != chl_list->current)
+ {
+ gboolean ok = FALSE;
+ char *text;
+
+ listbox_get_current (chl_list, &text, NULL);
+ if (is_owner)
+ {
+ chl_pass = getpwnam (text);
+ if (chl_pass != NULL)
+ {
+ sf_stat.st_uid = chl_pass->pw_uid;
+ ok = TRUE;
+ }
+ }
+ else
+ {
+ chl_grp = getgrnam (text);
+ if (chl_grp != NULL)
+ {
+ sf_stat.st_gid = chl_grp->gr_gid;
+ ok = TRUE;
+ }
+ }
+
+ if (!ok)
+ group_select_current_widget (g);
+ else
+ {
+ ch_flags[f_pos + 6] = '+';
+ update_ownership ();
+ group_select_current_widget (g);
+ print_flags (h);
+ }
+ }
+
+ if (result == KEY_LEFT)
+ {
+ if (!is_owner)
+ chl_end = TRUE;
+ group_select_prev_widget (g);
+ f_pos--;
+ }
+ else if (result == KEY_RIGHT)
+ {
+ if (is_owner)
+ chl_end = TRUE;
+ group_select_next_widget (g);
+ f_pos++;
+ }
+ }
+
+ /* Here we used to redraw the window */
+ widget_destroy (WIDGET (chl_dlg));
+ }
+ while (chl_end);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+advanced_chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ advanced_chown_refresh (DIALOG (w->owner));
+ advanced_chown_info_update ();
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+advanced_chown_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ int i = 0;
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case ALT ('x'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case ALT ('w'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case ALT ('r'):
+ parm = i + 3;
+ for (i = 0; i < 3; i++)
+ ch_flags[i * 3 + parm - 3] = (x_toggle & (1 << parm)) ? '-' : '+';
+ x_toggle ^= (1 << parm);
+ update_mode (g);
+ widget_draw (w);
+ break;
+
+ case XCTRL ('x'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case XCTRL ('w'):
+ i++;
+ MC_FALLTHROUGH;
+
+ case XCTRL ('r'):
+ parm = i;
+ for (i = 0; i < 3; i++)
+ ch_flags[i * 3 + parm] = (x_toggle & (1 << parm)) ? '-' : '+';
+ x_toggle ^= (1 << parm);
+ update_mode (g);
+ widget_draw (w);
+ break;
+
+ default:
+ break;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+advanced_chown_dlg_create (WPanel * panel)
+{
+ gboolean single_set;
+ WDialog *ch_dlg;
+ WGroup *ch_grp;
+ int lines = 12;
+ int cols = 74;
+ int i;
+ int y;
+
+ memset (ch_flags, '=', 11);
+ flag_pos = 0;
+ x_toggle = 070;
+
+ single_set = (panel->marked < 2);
+ if (!single_set)
+ lines += 2;
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
+ advanced_chown_callback, NULL, "[Advanced Chown]", _("Chown advanced command"));
+ ch_grp = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = advanced_chown_bg_callback;
+
+ l_filename = label_new (2, 3, NULL);
+ group_add_widget (ch_grp, l_filename);
+
+ group_add_widget (ch_grp, hline_new (3, -1, -1));
+
+#define XTRACT(i,y,cb) y, BX+advanced_chown_but[i].x, \
+ advanced_chown_but[i].ret_cmd, advanced_chown_but[i].flags, \
+ (advanced_chown_but[i].text), cb
+ b_att[0] = perm_button_new (XTRACT (0, BY, NULL));
+ advanced_chown_but[0].id = group_add_widget (ch_grp, b_att[0]);
+ b_att[1] = perm_button_new (XTRACT (1, BY, NULL));
+ advanced_chown_but[1].id = group_add_widget (ch_grp, b_att[1]);
+ b_att[2] = perm_button_new (XTRACT (2, BY, NULL));
+ advanced_chown_but[2].id = group_add_widget (ch_grp, b_att[2]);
+ b_user = button_new (XTRACT (3, BY, user_group_button_cb));
+ advanced_chown_but[3].id = group_add_widget (ch_grp, b_user);
+ b_group = button_new (XTRACT (4, BY, user_group_button_cb));
+ advanced_chown_but[4].id = group_add_widget (ch_grp, b_group);
+
+ l_mode = label_new (BY + 2, 3, NULL);
+ group_add_widget (ch_grp, l_mode);
+
+ y = BY + 3;
+ if (!single_set)
+ {
+ i = BUTTONS_PERM;
+ group_add_widget (ch_grp, hline_new (y++, -1, -1));
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 -
+ advanced_chown_but[i].len,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ i++;
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ y++;
+ }
+
+ i = BUTTONS_PERM + 2;
+ group_add_widget (ch_grp, hline_new (y++, -1, -1));
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y,
+ WIDGET (ch_dlg)->rect.cols / 2 -
+ advanced_chown_but[i].len,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+ i++;
+ advanced_chown_but[i].id = group_add_widget (ch_grp,
+ button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ advanced_chown_but[i].ret_cmd,
+ advanced_chown_but[i].flags,
+ advanced_chown_but[i].text, NULL));
+
+ widget_select (WIDGET (b_att[0]));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+advanced_chown_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_advanced_chown (const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
+{
+ int chmod_result;
+ const char *fname = NULL;
+
+ while ((chmod_result = mc_chmod (p, m)) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* call mc_chown() only, if mc_chmod() didn't fail */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* call mc_chown() only, if mc_chmod() didn't fail */
+ return TRUE;
+
+ case 2:
+ /* retry chmod of this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ /* call mc_chown() only, if mc_chmod didn't fail */
+ while (chmod_result != -1 && mc_chown (p, u, g) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry chown of this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_advanced_chown (WPanel * panel, const vfs_path_t * p, mode_t m, uid_t u, gid_t g)
+{
+ gboolean ret;
+
+ ret = try_advanced_chown (p, m, u, g);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+ /* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_advanced_chowns (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
+{
+ gid_t a_gid = sf->st_gid;
+ uid_t a_uid = sf->st_uid;
+ gboolean ok;
+
+ if (!do_advanced_chown (panel, vpath, get_mode (),
+ (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
+ (ch_flags[10] == '+') ? a_gid : (gid_t) (-1)))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ ch_cmode = sf->st_mode;
+
+ ok = do_advanced_chown (panel, vpath, get_mode (),
+ (ch_flags[9] == '+') ? a_uid : (uid_t) (-1),
+ (ch_flags[10] == '+') ? a_gid : (gid_t) (-1));
+ }
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+advanced_chown_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chown;
+
+ /* Number of files at startup */
+ int files_on_begin;
+
+ files_on_begin = MAX (1, panel->marked);
+
+ advanced_chown_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ const GString *fname;
+ int result;
+ int file_idx;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chown = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_cmode = sf_stat.st_mode;
+
+ ch_dlg = advanced_chown_dlg_create (panel);
+
+ file_idx = files_on_begin == 1 ? 1 : (files_on_begin - panel->marked + 1);
+ label_set_textv (l_filename, "%s (%d/%d)",
+ str_fit_to_term (fname->str, WIDGET (ch_dlg)->rect.cols - 20, J_LEFT_FIT),
+ file_idx, files_on_begin);
+ update_ownership ();
+
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chown = TRUE;
+ break;
+
+ case B_ENTER:
+ {
+ uid_t uid = ch_flags[9] == '+' ? sf_stat.st_uid : (uid_t) (-1);
+ gid_t gid = ch_flags[10] == '+' ? sf_stat.st_gid : (gid_t) (-1);
+
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chmod (vpath, get_mode ()) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"),
+ fname->str, unix_error_string (errno));
+ /* call mc_chown only, if mc_chmod didn't fail */
+ else if (mc_chown (vpath, uid, gid) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+
+ end_chown = TRUE;
+ }
+ else if (!try_advanced_chown (vpath, get_mode (), uid, gid))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chown = TRUE;
+ }
+
+ need_update = TRUE;
+ break;
+ }
+
+ case B_SETALL:
+ apply_advanced_chowns (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chown = TRUE;
+ break;
+
+ case B_SKIP:
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chown);
+
+ advanced_chown_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/boxes.c b/src/filemanager/boxes.c
new file mode 100644
index 0000000..e091c95
--- /dev/null
+++ b/src/filemanager/boxes.c
@@ -0,0 +1,1343 @@
+/*
+ Some misc dialog boxes for the program.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file boxes.c
+ * \brief Source: Some misc dialog boxes for the program
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h" /* tty_use_colors() */
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h" /* INPUT_COLOR */
+#include "lib/mcconfig.h" /* Load/save user formats */
+#include "lib/strutil.h"
+
+#include "lib/vfs/vfs.h"
+#ifdef ENABLE_VFS_FTP
+#include "src/vfs/ftpfs/ftpfs.h"
+#endif /* ENABLE_VFS_FTP */
+
+#include "lib/util.h" /* Q_() */
+#include "lib/widget.h"
+
+#include "src/setup.h"
+#include "src/history.h" /* MC_HISTORY_ESC_TIMEOUT */
+#include "src/execute.h" /* pause_after_run */
+#ifdef ENABLE_BACKGROUND
+#include "src/background.h" /* task_list */
+#endif
+
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h"
+#include "src/selcodepage.h"
+#endif
+
+#include "command.h" /* For cmdline */
+#include "dir.h"
+#include "tree.h"
+#include "layout.h" /* for get_nth_panel_name proto */
+#include "filemanager.h"
+
+#include "boxes.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef ENABLE_BACKGROUND
+#define B_STOP (B_USER+1)
+#define B_RESUME (B_USER+2)
+#define B_KILL (B_USER+3)
+#endif /* ENABLE_BACKGROUND */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static unsigned long configure_old_esc_mode_id, configure_time_out_id;
+
+/* Index in list_formats[] for "brief" */
+static const int panel_list_brief_idx = 1;
+/* Index in list_formats[] for "user defined" */
+static const int panel_list_user_idx = 3;
+
+static char **status_format;
+static unsigned long panel_list_formats_id, panel_user_format_id, panel_brief_cols_id;
+static unsigned long user_mini_status_id, mini_user_format_id;
+
+#ifdef HAVE_CHARSET
+static int new_display_codepage;
+#endif /* HAVE_CHARSET */
+
+#if defined(ENABLE_VFS) && defined(ENABLE_VFS_FTP)
+static unsigned long ftpfs_always_use_proxy_id, ftpfs_proxy_host_id;
+#endif /* ENABLE_VFS && ENABLE_VFS_FTP */
+
+static GPtrArray *skin_names;
+static gchar *current_skin_name;
+
+#ifdef ENABLE_BACKGROUND
+static WListbox *bg_list = NULL;
+#endif /* ENABLE_BACKGROUND */
+
+static unsigned long shadows_id;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+configure_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ /* message from "Single press" checkbutton */
+ if (sender != NULL && sender->id == configure_old_esc_mode_id)
+ {
+ const gboolean not_single = !CHECK (sender)->state;
+ Widget *ww;
+
+ /* input line */
+ ww = widget_find_by_id (w, configure_time_out_id);
+ widget_disable (ww, not_single);
+
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+skin_apply (const gchar * skin_override)
+{
+ GError *mcerror = NULL;
+
+ mc_skin_deinit ();
+ mc_skin_init (skin_override, &mcerror);
+ mc_fhl_free (&mc_filehighlight);
+ mc_filehighlight = mc_fhl_new (TRUE);
+ dlg_set_default_colors ();
+ input_set_default_colors ();
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ command_set_default_colors ();
+ panel_deinit ();
+ panel_init ();
+ repaint_screen ();
+
+ mc_error_message (&mcerror, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const gchar *
+skin_name_to_label (const gchar * name)
+{
+ if (strcmp (name, "default") == 0)
+ return _("< Default >");
+ return name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+skin_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ {
+ WDialog *d = DIALOG (w);
+ const WRect *wd = &WIDGET (d->data.p)->rect;
+ WRect r = w->rect;
+
+ r.y = wd->y + (wd->lines - r.lines) / 2;
+ r.x = wd->x + wd->cols / 2;
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+sel_skin_button (WButton * button, int action)
+{
+ int result;
+ WListbox *skin_list;
+ WDialog *skin_dlg;
+ const gchar *skin_name;
+ unsigned int i;
+ unsigned int pos = 1;
+
+ (void) action;
+
+ skin_dlg =
+ dlg_create (TRUE, 0, 0, 13, 24, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, skin_dlg_callback,
+ NULL, "[Appearance]", _("Skins"));
+ /* use Appearance dialog for positioning */
+ skin_dlg->data.p = WIDGET (button)->owner;
+
+ /* set dialog location before all */
+ send_message (skin_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ skin_list = listbox_new (1, 1, 11, 22, FALSE, NULL);
+ skin_name = "default";
+ listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
+ (void *) skin_name, FALSE);
+
+ if (strcmp (skin_name, current_skin_name) == 0)
+ listbox_set_current (skin_list, 0);
+
+ for (i = 0; i < skin_names->len; i++)
+ {
+ skin_name = g_ptr_array_index (skin_names, i);
+ if (strcmp (skin_name, "default") != 0)
+ {
+ listbox_add_item (skin_list, LISTBOX_APPEND_AT_END, 0, skin_name_to_label (skin_name),
+ (void *) skin_name, FALSE);
+ if (strcmp (skin_name, current_skin_name) == 0)
+ listbox_set_current (skin_list, pos);
+ pos++;
+ }
+ }
+
+ /* make list stick to all sides of dialog, effectively make it be resized with dialog */
+ group_add_widget_autopos (GROUP (skin_dlg), skin_list, WPOS_KEEP_ALL, NULL);
+
+ result = dlg_run (skin_dlg);
+ if (result == B_ENTER)
+ {
+ gchar *skin_label;
+
+ listbox_get_current (skin_list, &skin_label, (void **) &skin_name);
+ g_free (current_skin_name);
+ current_skin_name = g_strdup (skin_name);
+ skin_apply (skin_name);
+
+ button_set_text (button, str_fit_to_term (skin_label, 20, J_LEFT_FIT));
+ }
+ widget_destroy (WIDGET (skin_dlg));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+appearance_box_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+#ifdef ENABLE_SHADOWS
+ if (!tty_use_colors ())
+#endif
+ {
+ Widget *shadow;
+
+ shadow = widget_find_by_id (w, shadows_id);
+ CHECK (shadow)->state = FALSE;
+ widget_disable (shadow, TRUE);
+ }
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender != NULL && sender->id == shadows_id)
+ {
+ mc_global.tty.shadows = CHECK (sender)->state;
+ repaint_screen ();
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_listing_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ if (sender != NULL && sender->id == panel_list_formats_id)
+ {
+ WCheck *ch;
+ WInput *in1, *in2, *in3;
+
+ in1 = INPUT (widget_find_by_id (w, panel_user_format_id));
+ in2 = INPUT (widget_find_by_id (w, panel_brief_cols_id));
+ ch = CHECK (widget_find_by_id (w, user_mini_status_id));
+ in3 = INPUT (widget_find_by_id (w, mini_user_format_id));
+
+ if (!ch->state)
+ input_assign_text (in3, status_format[RADIO (sender)->sel]);
+ input_update (in1, FALSE);
+ input_update (in2, FALSE);
+ input_update (in3, FALSE);
+ widget_disable (WIDGET (in1), RADIO (sender)->sel != panel_list_user_idx);
+ widget_disable (WIDGET (in2), RADIO (sender)->sel != panel_list_brief_idx);
+ return MSG_HANDLED;
+ }
+
+ if (sender != NULL && sender->id == user_mini_status_id)
+ {
+ WInput *in;
+
+ in = INPUT (widget_find_by_id (w, mini_user_format_id));
+
+ if (CHECK (sender)->state)
+ {
+ widget_disable (WIDGET (in), FALSE);
+ input_assign_text (in, status_format[3]);
+ }
+ else
+ {
+ WRadio *r;
+
+ r = RADIO (widget_find_by_id (w, panel_list_formats_id));
+ widget_disable (WIDGET (in), TRUE);
+ input_assign_text (in, status_format[r->sel]);
+ }
+ /* input_update (in, FALSE); */
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+static int
+sel_charset_button (WButton * button, int action)
+{
+ int new_dcp;
+
+ (void) action;
+
+ new_dcp = select_charset (-1, -1, new_display_codepage, TRUE);
+
+ if (new_dcp != SELECT_CHARSET_CANCEL)
+ {
+ const char *cpname;
+
+ new_display_codepage = new_dcp;
+ cpname = (new_display_codepage == SELECT_CHARSET_OTHER_8BIT) ?
+ _("Other 8 bit") :
+ ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;
+ if (cpname != NULL)
+ mc_global.utf8_display = str_isutf8 (cpname);
+ else
+ cpname = _("7-bit ASCII"); /* FIXME */
+
+ button_set_text (button, cpname);
+ widget_draw (WIDGET (WIDGET (button)->owner));
+ }
+
+ return 0;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ {
+ WRect r = w->rect;
+ Widget *bar;
+
+ r.lines = LINES - 9;
+ r.cols = COLS - 20;
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+
+ bar = WIDGET (buttonbar_find (h));
+ bar->rect.x = 0;
+ bar->rect.y = LINES - 1;
+ return MSG_HANDLED;
+ }
+
+ case MSG_ACTION:
+ return send_message (find_tree (h), NULL, MSG_ACTION, parm, NULL);
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined(ENABLE_VFS) && defined (ENABLE_VFS_FTP)
+static cb_ret_t
+confvfs_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ /* message from "Always use ftp proxy" checkbutton */
+ if (sender != NULL && sender->id == ftpfs_always_use_proxy_id)
+ {
+ const gboolean not_use = !CHECK (sender)->state;
+ Widget *wi;
+
+ /* input */
+ wi = widget_find_by_id (w, ftpfs_proxy_host_id);
+ widget_disable (wi, not_use);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+#endif /* ENABLE_VFS && ENABLE_VFS_FTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static void
+jobs_fill_listbox (WListbox * list)
+{
+ static const char *state_str[2] = { "", "" };
+ TaskList *tl;
+
+ if (state_str[0][0] == '\0')
+ {
+ state_str[0] = _("Running");
+ state_str[1] = _("Stopped");
+ }
+
+ for (tl = task_list; tl != NULL; tl = tl->next)
+ {
+ char *s;
+
+ s = g_strconcat (state_str[tl->state], " ", tl->info, (char *) NULL);
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, s, (void *) tl, FALSE);
+ g_free (s);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+task_cb (WButton * button, int action)
+{
+ TaskList *tl;
+ int sig = 0;
+
+ (void) button;
+
+ if (bg_list->list == NULL)
+ return 0;
+
+ /* Get this instance information */
+ listbox_get_current (bg_list, NULL, (void **) &tl);
+
+#ifdef SIGTSTP
+ if (action == B_STOP)
+ {
+ sig = SIGSTOP;
+ tl->state = Task_Stopped;
+ }
+ else if (action == B_RESUME)
+ {
+ sig = SIGCONT;
+ tl->state = Task_Running;
+ }
+ else
+#endif
+ if (action == B_KILL)
+ sig = SIGKILL;
+
+ if (sig == SIGKILL)
+ unregister_task_running (tl->pid, tl->fd);
+
+ kill (tl->pid, sig);
+ listbox_remove_list (bg_list);
+ jobs_fill_listbox (bg_list);
+
+ /* This can be optimized to just redraw this widget :-) */
+ widget_draw (WIDGET (WIDGET (button)->owner));
+
+ return 0;
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+configure_box (void)
+{
+ const char *pause_options[] = {
+ N_("&Never"),
+ N_("On dum&b terminals"),
+ N_("Alwa&ys")
+ };
+
+ int pause_options_num;
+
+ pause_options_num = G_N_ELEMENTS (pause_options);
+
+ {
+ char time_out[BUF_TINY] = "";
+ char *time_out_new;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("File operations")),
+ QUICK_CHECKBOX (N_("&Verbose operation"), &verbose, NULL),
+ QUICK_CHECKBOX (N_("Compute tota&ls"), &file_op_compute_totals, NULL),
+ QUICK_CHECKBOX (N_("Classic pro&gressbar"), &classic_progressbar, NULL),
+ QUICK_CHECKBOX (N_("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL),
+ QUICK_CHECKBOX (N_("&Preallocate space"), &mc_global.vfs.preallocate_space,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Esc key mode")),
+ QUICK_CHECKBOX (N_("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id),
+ QUICK_LABELED_INPUT (N_("Timeout:"), input_label_left,
+ (const char *) time_out, MC_HISTORY_ESC_TIMEOUT,
+ &time_out_new, &configure_time_out_id, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Pause after run")),
+ QUICK_RADIO (pause_options_num, pause_options, &pause_after_run, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Other options")),
+ QUICK_CHECKBOX (N_("Use internal edi&t"), &use_internal_edit, NULL),
+ QUICK_CHECKBOX (N_("Use internal vie&w"), &use_internal_view, NULL),
+ QUICK_CHECKBOX (N_("A&sk new file name"),
+ &editor_ask_filename_before_edit, NULL),
+ QUICK_CHECKBOX (N_("Auto m&enus"), &auto_menu, NULL),
+ QUICK_CHECKBOX (N_("&Drop down menus"), &drop_menus, NULL),
+ QUICK_CHECKBOX (N_("S&hell patterns"), &easy_patterns, NULL),
+ QUICK_CHECKBOX (N_("Co&mplete: show all"),
+ &mc_global.widget.show_all_if_ambiguous, NULL),
+ QUICK_CHECKBOX (N_("Rotating d&ash"), &nice_rotating_dash, NULL),
+ QUICK_CHECKBOX (N_("Cd follows lin&ks"), &mc_global.vfs.cd_symlinks, NULL),
+ QUICK_CHECKBOX (N_("Sa&fe delete"), &safe_delete, NULL),
+ QUICK_CHECKBOX (N_("Safe overwrite"), &safe_overwrite, NULL), /* w/o hotkey */
+ QUICK_CHECKBOX (N_("A&uto save setup"), &auto_save_setup, NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 60 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Configure options"), "[Configuration]",
+ quick_widgets, configure_callback, NULL
+ };
+
+ g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout);
+
+#ifndef USE_INTERNAL_EDIT
+ quick_widgets[17].state = WST_DISABLED;
+#endif
+
+ if (!old_esc_mode)
+ quick_widgets[10].state = quick_widgets[11].state = WST_DISABLED;
+
+#ifndef HAVE_POSIX_FALLOCATE
+ mc_global.vfs.preallocate_space = FALSE;
+ quick_widgets[7].state = WST_DISABLED;
+#endif
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ {
+ if (time_out_new[0] == '\0')
+ old_esc_mode_timeout = 0;
+ else
+ old_esc_mode_timeout = atoi (time_out_new);
+ }
+
+ g_free (time_out_new);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+appearance_box (void)
+{
+ gboolean shadows = mc_global.tty.shadows;
+
+ current_skin_name = g_strdup (mc_skin__default.name);
+ skin_names = mc_skin_list ();
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_LABEL (N_("Skin:"), NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_BUTTON (str_fit_to_term (skin_name_to_label (current_skin_name), 20, J_LEFT_FIT),
+ B_USER, sel_skin_button, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("&Shadows"), &mc_global.tty.shadows, &shadows_id),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 54 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Appearance"), "[Appearance]",
+ quick_widgets, appearance_box_callback, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ mc_config_set_string (mc_global.main_config, CONFIG_APP_SECTION, "skin",
+ current_skin_name);
+ else
+ {
+ skin_apply (NULL);
+ mc_global.tty.shadows = shadows;
+ }
+ }
+
+ g_free (current_skin_name);
+ g_ptr_array_free (skin_names, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_options_box (void)
+{
+ gboolean simple_swap;
+
+ simple_swap = mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION,
+ "simple_swap", FALSE);
+ {
+ const char *qsearch_options[] = {
+ N_("Case &insensitive"),
+ N_("Cas&e sensitive"),
+ N_("Use panel sort mo&de")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_START_GROUPBOX (N_("Main options")),
+ QUICK_CHECKBOX (N_("Show mi&ni-status"), &panels_options.show_mini_info, NULL),
+ QUICK_CHECKBOX (N_("Use SI si&ze units"), &panels_options.kilobyte_si, NULL),
+ QUICK_CHECKBOX (N_("Mi&x all files"), &panels_options.mix_all_files, NULL),
+ QUICK_CHECKBOX (N_("Show &backup files"), &panels_options.show_backups, NULL),
+ QUICK_CHECKBOX (N_("Show &hidden files"), &panels_options.show_dot_files, NULL),
+ QUICK_CHECKBOX (N_("&Fast dir reload"), &panels_options.fast_reload, NULL),
+ QUICK_CHECKBOX (N_("Ma&rk moves down"), &panels_options.mark_moves_down, NULL),
+ QUICK_CHECKBOX (N_("Re&verse files only"), &panels_options.reverse_files_only,
+ NULL),
+ QUICK_CHECKBOX (N_("Simple s&wap"), &simple_swap, NULL),
+ QUICK_CHECKBOX (N_("A&uto save panels setup"), &panels_options.auto_save_setup,
+ NULL),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_STOP_GROUPBOX,
+ QUICK_NEXT_COLUMN,
+ QUICK_START_GROUPBOX (N_("Navigation")),
+ QUICK_CHECKBOX (N_("L&ynx-like motion"), &panels_options.navigate_with_arrows,
+ NULL),
+ QUICK_CHECKBOX (N_("Pa&ge scrolling"), &panels_options.scroll_pages, NULL),
+ QUICK_CHECKBOX (N_("Center &scrolling"), &panels_options.scroll_center, NULL),
+ QUICK_CHECKBOX (N_("&Mouse page scrolling"), &panels_options.mouse_move_pages,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("File highlight")),
+ QUICK_CHECKBOX (N_("File &types"), &panels_options.filetype_mode, NULL),
+ QUICK_CHECKBOX (N_("&Permissions"), &panels_options.permission_mode, NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_START_GROUPBOX (N_("Quick search")),
+ QUICK_RADIO (QSEARCH_NUM, qsearch_options, (int *) &panels_options.qsearch_mode,
+ NULL),
+ QUICK_STOP_GROUPBOX,
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 60 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Panel options"), "[Panel options]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_ENTER)
+ return;
+ }
+
+ mc_config_set_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", simple_swap);
+
+ if (!panels_options.fast_reload_msg_shown && panels_options.fast_reload)
+ {
+ message (D_NORMAL, _("Information"),
+ _("Using the fast reload option may not reflect the exact\n"
+ "directory contents. In this case you'll need to do a\n"
+ "manual reload of the directory. See the man page for\n" "the details."));
+ panels_options.fast_reload_msg_shown = TRUE;
+ }
+
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* return list type */
+int
+panel_listing_box (WPanel * panel, int num, char **userp, char **minip, gboolean * use_msformat,
+ int *brief_cols)
+{
+ int result = -1;
+ const char *p = NULL;
+
+ if (panel == NULL)
+ {
+ p = get_nth_panel_name (num);
+ panel = panel_empty_new (p);
+ }
+
+ {
+ gboolean user_mini_status;
+ char panel_brief_cols_in[BUF_TINY];
+ char *panel_brief_cols_out = NULL;
+ char *panel_user_format = NULL;
+ char *mini_user_format = NULL;
+
+ /* Controls whether the array strings have been translated */
+ const char *list_formats[LIST_FORMATS] = {
+ N_("&Full file list"),
+ N_("&Brief file list:"),
+ N_("&Long file list"),
+ N_("&User defined:")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (LIST_FORMATS, list_formats, &result, &panel_list_formats_id),
+ QUICK_NEXT_COLUMN,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (_ ("columns"), input_label_right, panel_brief_cols_in,
+ "panel-brief-cols-input", &panel_brief_cols_out,
+ &panel_brief_cols_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_STOP_COLUMNS,
+ QUICK_INPUT (panel->user_format, "user-fmt-input", &panel_user_format,
+ &panel_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("User &mini status"), &user_mini_status, &user_mini_status_id),
+ QUICK_INPUT (panel->user_status_format[panel->list_format], "mini_input",
+ &mini_user_format, &mini_user_format_id, FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 48 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Listing format"), "[Listing Format...]",
+ quick_widgets, panel_listing_callback, NULL
+ };
+
+ user_mini_status = panel->user_mini_status;
+ result = panel->list_format;
+ status_format = panel->user_status_format;
+
+ g_snprintf (panel_brief_cols_in, sizeof (panel_brief_cols_in), "%d", panel->brief_cols);
+
+ if ((int) panel->list_format != panel_list_brief_idx)
+ quick_widgets[4].state = WST_DISABLED;
+
+ if ((int) panel->list_format != panel_list_user_idx)
+ quick_widgets[6].state = WST_DISABLED;
+
+ if (!user_mini_status)
+ quick_widgets[9].state = WST_DISABLED;
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ result = -1;
+ else
+ {
+ int cols;
+ char *error = NULL;
+
+ *userp = panel_user_format;
+ *minip = mini_user_format;
+ *use_msformat = user_mini_status;
+
+ cols = strtol (panel_brief_cols_out, &error, 10);
+ if (*error == '\0')
+ *brief_cols = cols;
+ else
+ *brief_cols = panel->brief_cols;
+
+ g_free (panel_brief_cols_out);
+ }
+ }
+
+ if (p != NULL)
+ {
+ int i;
+
+ g_free (panel->user_format);
+ for (i = 0; i < LIST_FORMATS; i++)
+ g_free (panel->user_status_format[i]);
+ g_free (panel);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+sort_box (dir_sort_options_t * op, const panel_field_t * sort_field)
+{
+ char **sort_orders_names;
+ gsize i;
+ gsize sort_names_num = 0;
+ int sort_idx = 0;
+ const panel_field_t *result = NULL;
+
+ sort_orders_names = panel_get_sortable_fields (&sort_names_num);
+
+ for (i = 0; i < sort_names_num; i++)
+ if (strcmp (sort_orders_names[i], _(sort_field->title_hotkey)) == 0)
+ {
+ sort_idx = i;
+ break;
+ }
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_RADIO (sort_names_num, (const char **) sort_orders_names, &sort_idx, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Executable &first"), &op->exec_first, NULL),
+ QUICK_CHECKBOX (N_("Cas&e sensitive"), &op->case_sensitive, NULL),
+ QUICK_CHECKBOX (N_("&Reverse"), &op->reverse, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 40 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Sort order"), "[Sort Order...]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ result = panel_get_field_by_title_hotkey (sort_orders_names[sort_idx]);
+
+ if (result == NULL)
+ result = sort_field;
+ }
+
+ g_strfreev (sort_orders_names);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+confirm_box (void)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ /* TRANSLATORS: no need to translate 'Confirmation', it's just a context prefix */
+ QUICK_CHECKBOX (Q_("Confirmation|&Delete"), &confirm_delete, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|O&verwrite"), &confirm_overwrite, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|&Execute"), &confirm_execute, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|E&xit"), &confirm_exit, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|Di&rectory hotlist delete"),
+ &confirm_directory_hotlist_delete, NULL),
+ QUICK_CHECKBOX (Q_("Confirmation|&History cleanup"),
+ &mc_global.widget.confirm_history_cleanup, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Confirmation"), "[Confirmation]",
+ quick_widgets, NULL, NULL
+ };
+
+ (void) quick_dialog (&qdlg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifndef HAVE_CHARSET
+void
+display_bits_box (void)
+{
+ gboolean new_meta;
+ int current_mode;
+
+ const char *display_bits_str[] = {
+ N_("&UTF-8 output"),
+ N_("&Full 8 bits output"),
+ N_("&ISO 8859-1"),
+ N_("7 &bits")
+ };
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_RADIO (4, display_bits_str, &current_mode, NULL),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, _("Display bits"), "[Display bits]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (mc_global.full_eight_bits)
+ current_mode = 0;
+ else if (mc_global.eight_bit_clean)
+ current_mode = 1;
+ else
+ current_mode = 2;
+
+ new_meta = !use_8th_bit_as_meta;
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ mc_global.eight_bit_clean = current_mode < 3;
+ mc_global.full_eight_bits = current_mode < 2;
+#ifndef HAVE_SLANG
+ tty_display_8bit (mc_global.eight_bit_clean);
+#else
+ tty_display_8bit (mc_global.full_eight_bits);
+#endif
+ use_8th_bit_as_meta = !new_meta;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+#else /* HAVE_CHARSET */
+
+void
+display_bits_box (void)
+{
+ const char *cpname;
+
+ new_display_codepage = mc_global.display_codepage;
+
+ cpname = (new_display_codepage < 0) ? _("Other 8 bit")
+ : ((codepage_desc *) g_ptr_array_index (codepages, new_display_codepage))->name;
+
+ {
+ gboolean new_meta;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_START_COLUMNS,
+ QUICK_LABEL (N_("Input / display codepage:"), NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_BUTTON (cpname, B_USER, sel_charset_button, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_SEPARATOR (TRUE),
+ QUICK_CHECKBOX (N_("F&ull 8 bits input"), &new_meta, NULL),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 46 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Display bits"), "[Display bits]",
+ quick_widgets, NULL, NULL
+ };
+
+ new_meta = !use_8th_bit_as_meta;
+ application_keypad_mode ();
+
+ if (quick_dialog (&qdlg) == B_ENTER)
+ {
+ char *errmsg;
+
+ mc_global.display_codepage = new_display_codepage;
+
+ errmsg = init_translation_table (mc_global.source_codepage, mc_global.display_codepage);
+ if (errmsg != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, "%s", errmsg);
+ g_free (errmsg);
+ }
+
+#ifdef HAVE_SLANG
+ tty_display_8bit (mc_global.display_codepage != 0 && mc_global.display_codepage != 1);
+#else
+ tty_display_8bit (mc_global.display_codepage != 0);
+#endif
+ use_8th_bit_as_meta = !new_meta;
+
+ repaint_screen ();
+ }
+ }
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show tree in a box, not on a panel */
+
+char *
+tree_box (const char *current_dir)
+{
+ WTree *mytree;
+ WDialog *dlg;
+ WGroup *g;
+ Widget *wd;
+ char *val = NULL;
+ WButtonBar *bar;
+
+ (void) current_dir;
+
+ /* Create the components */
+ dlg = dlg_create (TRUE, 0, 0, LINES - 9, COLS - 20, WPOS_CENTER, FALSE, dialog_colors,
+ tree_callback, NULL, "[Directory Tree]", _("Directory tree"));
+ g = GROUP (dlg);
+ wd = WIDGET (dlg);
+
+ mytree = tree_new (2, 2, wd->rect.lines - 6, wd->rect.cols - 5, FALSE);
+ group_add_widget_autopos (g, mytree, WPOS_KEEP_ALL, NULL);
+ group_add_widget_autopos (g, hline_new (wd->rect.lines - 4, 1, -1), WPOS_KEEP_BOTTOM, NULL);
+ bar = buttonbar_new ();
+ group_add_widget (g, bar);
+ /* restore ButtonBar coordinates after add_widget() */
+ WIDGET (bar)->rect.x = 0;
+ WIDGET (bar)->rect.y = LINES - 1;
+
+ if (dlg_run (dlg) == B_ENTER)
+ {
+ const vfs_path_t *selected_name;
+
+ selected_name = tree_selected_name (mytree);
+ val = g_strdup (vfs_path_as_str (selected_name));
+ }
+
+ widget_destroy (wd);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+void
+configure_vfs_box (void)
+{
+ char buffer2[BUF_TINY];
+#ifdef ENABLE_VFS_FTP
+ char buffer3[BUF_TINY];
+
+ g_snprintf (buffer3, sizeof (buffer3), "%i", ftpfs_directory_timeout);
+#endif
+
+ g_snprintf (buffer2, sizeof (buffer2), "%i", vfs_timeout);
+
+ {
+ char *ret_timeout;
+#ifdef ENABLE_VFS_FTP
+ char *ret_passwd;
+ char *ret_ftp_proxy;
+ char *ret_directory_timeout;
+#endif /* ENABLE_VFS_FTP */
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Timeout for freeing VFSs (sec):"), input_label_left,
+ buffer2, "input-timo-vfs", &ret_timeout, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_NONE),
+#ifdef ENABLE_VFS_FTP
+ QUICK_SEPARATOR (TRUE),
+ QUICK_LABELED_INPUT (N_("FTP anonymous password:"), input_label_left,
+ ftpfs_anonymous_passwd, "input-passwd", &ret_passwd, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_LABELED_INPUT (N_("FTP directory cache timeout (sec):"), input_label_left,
+ buffer3, "input-timeout", &ret_directory_timeout, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_CHECKBOX (N_("&Always use ftp proxy:"), &ftpfs_always_use_proxy,
+ &ftpfs_always_use_proxy_id),
+ QUICK_INPUT (ftpfs_proxy_host, "input-ftp-proxy", &ret_ftp_proxy,
+ &ftpfs_proxy_host_id, FALSE, FALSE, INPUT_COMPLETE_HOSTNAMES),
+ QUICK_CHECKBOX (N_("&Use ~/.netrc"), &ftpfs_use_netrc, NULL),
+ QUICK_CHECKBOX (N_("Use &passive mode"), &ftpfs_use_passive_connections, NULL),
+ QUICK_CHECKBOX (N_("Use passive mode over pro&xy"),
+ &ftpfs_use_passive_connections_over_proxy, NULL),
+#endif /* ENABLE_VFS_FTP */
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 56 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Virtual File System Setting"), "[Virtual FS]",
+ quick_widgets,
+#ifdef ENABLE_VFS_FTP
+ confvfs_callback,
+#else
+ NULL,
+#endif
+ NULL,
+ };
+
+#ifdef ENABLE_VFS_FTP
+ if (!ftpfs_always_use_proxy)
+ quick_widgets[5].state = WST_DISABLED;
+#endif
+
+ if (quick_dialog (&qdlg) != B_CANCEL)
+ {
+ /* cppcheck-suppress uninitvar */
+ if (ret_timeout[0] == '\0')
+ vfs_timeout = 0;
+ else
+ vfs_timeout = atoi (ret_timeout);
+ g_free (ret_timeout);
+
+ if (vfs_timeout < 0 || vfs_timeout > 10000)
+ vfs_timeout = 10;
+#ifdef ENABLE_VFS_FTP
+ g_free (ftpfs_anonymous_passwd);
+ /* cppcheck-suppress uninitvar */
+ ftpfs_anonymous_passwd = ret_passwd;
+ g_free (ftpfs_proxy_host);
+ /* cppcheck-suppress uninitvar */
+ ftpfs_proxy_host = ret_ftp_proxy;
+ /* cppcheck-suppress uninitvar */
+ if (ret_directory_timeout[0] == '\0')
+ ftpfs_directory_timeout = 0;
+ else
+ ftpfs_directory_timeout = atoi (ret_directory_timeout);
+ g_free (ret_directory_timeout);
+#endif
+ }
+ }
+}
+
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+cd_box (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+ char *my_str;
+
+ quick_widget_t quick_widgets[] = {
+ QUICK_LABELED_INPUT (N_("cd"), input_label_left, "", "input", &my_str, NULL, FALSE, TRUE,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
+ QUICK_END
+ };
+
+ WRect r = { w->rect.y + w->rect.lines - 6, w->rect.x, 0, w->rect.cols };
+
+ quick_dialog_t qdlg = {
+ r, N_("Quick cd"), "[Quick cd]",
+ quick_widgets, NULL, NULL
+ };
+
+ return (quick_dialog (&qdlg) != B_CANCEL) ? my_str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath,
+ char **ret_existing, char **ret_new)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (N_("Existing filename (filename symlink will point to):"),
+ input_label_above, vfs_path_as_str (existing_vpath), "input-2",
+ ret_existing, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (N_("Symbolic link filename:"), input_label_above,
+ vfs_path_as_str (new_vpath), "input-1",
+ ret_new, NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, N_("Symbolic link"), "[File Menu]",
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ {
+ *ret_new = NULL;
+ *ret_existing = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+void
+jobs_box (void)
+{
+ struct
+ {
+ const char *name;
+ int flags;
+ int value;
+ int len;
+ bcback_fn callback;
+ }
+ job_but[] =
+ {
+ /* *INDENT-OFF* */
+ { N_("&Stop"), NORMAL_BUTTON, B_STOP, 0, task_cb },
+ { N_("&Resume"), NORMAL_BUTTON, B_RESUME, 0, task_cb },
+ { N_("&Kill"), NORMAL_BUTTON, B_KILL, 0, task_cb },
+ { N_("&OK"), DEFPUSH_BUTTON, B_CANCEL, 0, NULL }
+ /* *INDENT-ON* */
+ };
+
+ size_t i;
+ const size_t n_but = G_N_ELEMENTS (job_but);
+
+ WDialog *jobs_dlg;
+ WGroup *g;
+ int cols = 60;
+ int lines = 15;
+ int x = 0;
+
+ for (i = 0; i < n_but; i++)
+ {
+#ifdef ENABLE_NLS
+ job_but[i].name = _(job_but[i].name);
+#endif /* ENABLE_NLS */
+
+ job_but[i].len = str_term_width1 (job_but[i].name) + 3;
+ if (job_but[i].flags == DEFPUSH_BUTTON)
+ job_but[i].len += 2;
+ x += job_but[i].len;
+ }
+
+ x += (int) n_but - 1;
+ cols = MAX (cols, x + 6);
+
+ jobs_dlg = dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL,
+ "[Background jobs]", _("Background jobs"));
+ g = GROUP (jobs_dlg);
+
+ bg_list = listbox_new (2, 2, lines - 6, cols - 6, FALSE, NULL);
+ jobs_fill_listbox (bg_list);
+ group_add_widget (g, bg_list);
+
+ group_add_widget (g, hline_new (lines - 4, -1, -1));
+
+ x = (cols - x) / 2;
+ for (i = 0; i < n_but; i++)
+ {
+ group_add_widget (g, button_new (lines - 3, x, job_but[i].value, job_but[i].flags,
+ job_but[i].name, job_but[i].callback));
+ x += job_but[i].len + 1;
+ }
+
+ (void) dlg_run (jobs_dlg);
+ widget_destroy (WIDGET (jobs_dlg));
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/boxes.h b/src/filemanager/boxes.h
new file mode 100644
index 0000000..6cb115e
--- /dev/null
+++ b/src/filemanager/boxes.h
@@ -0,0 +1,37 @@
+/** \file boxes.h
+ * \brief Header: Some misc dialog boxes for the program
+ */
+
+#ifndef MC__BOXES_H
+#define MC__BOXES_H
+
+#include "dir.h"
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void configure_box (void);
+void appearance_box (void);
+void panel_options_box (void);
+int panel_listing_box (WPanel * p, int num, char **user, char **mini, gboolean * use_msformat,
+ int *brief_cols);
+const panel_field_t *sort_box (dir_sort_options_t * op, const panel_field_t * sort_field);
+void confirm_box (void);
+void display_bits_box (void);
+void configure_vfs_box (void);
+void jobs_box (void);
+char *cd_box (const WPanel * panel);
+void symlink_box (const vfs_path_t * existing_vpath, const vfs_path_t * new_vpath,
+ char **ret_existing, char **ret_new);
+char *tree_box (const char *current_dir);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__BOXES_H */
diff --git a/src/filemanager/cd.c b/src/filemanager/cd.c
new file mode 100644
index 0000000..564a605
--- /dev/null
+++ b/src/filemanager/cd.c
@@ -0,0 +1,310 @@
+/*
+ cd_to() function.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2020
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file cd.c
+ * \brief Source: cd_to() function
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h" /* strutils_shell_unescape() */
+#include "lib/util.h" /* whitespace() */
+#include "lib/widget.h" /* message() */
+
+#include "filemanager.h" /* current_panel, panel.h, layout.h */
+#include "tree.h" /* sync_tree() */
+
+#include "cd.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Expand the argument to "cd" and change directory. First try tilde
+ * expansion, then variable substitution. If the CDPATH variable is set
+ * (e.g. CDPATH=".:~:/usr"), try all the paths contained there.
+ * We do not support such rare substitutions as ${var:-value} etc.
+ * No quoting is implemented here, so ${VAR} and $VAR will be always
+ * substituted. Wildcards are not supported either.
+ * Advanced users should be encouraged to use "\cd" instead of "cd" if
+ * they want the behavior they are used to in the shell.
+ *
+ * @param _path string to examine
+ * @return newly allocated string
+ */
+
+static GString *
+examine_cd (const char *_path)
+{
+ /* *INDENT-OFF* */
+ typedef enum
+ {
+ copy_sym,
+ subst_var
+ } state_t;
+ /* *INDENT-ON* */
+
+ state_t state = copy_sym;
+ GString *q;
+ char *path_tilde, *path;
+ char *p;
+
+ /* Tilde expansion */
+ path = strutils_shell_unescape (_path);
+ path_tilde = tilde_expand (path);
+ g_free (path);
+
+ q = g_string_sized_new (32);
+
+ /* Variable expansion */
+ for (p = path_tilde; *p != '\0';)
+ {
+ switch (state)
+ {
+ case copy_sym:
+ if (p[0] == '\\' && p[1] == '$')
+ {
+ g_string_append_c (q, '$');
+ p += 2;
+ }
+ else if (p[0] != '$' || p[1] == '[' || p[1] == '(')
+ {
+ g_string_append_c (q, *p);
+ p++;
+ }
+ else
+ state = subst_var;
+ break;
+
+ case subst_var:
+ {
+ char *s = NULL;
+ char c;
+ const char *t = NULL;
+
+ /* skip dollar */
+ p++;
+
+ if (p[0] == '{')
+ {
+ p++;
+ s = strchr (p, '}');
+ }
+ if (s == NULL)
+ s = strchr (p, PATH_SEP);
+ if (s == NULL)
+ s = strchr (p, '\0');
+ c = *s;
+ *s = '\0';
+ t = getenv (p);
+ *s = c;
+ if (t == NULL)
+ {
+ g_string_append_c (q, '$');
+ if (p[-1] != '$')
+ g_string_append_c (q, '{');
+ }
+ else
+ {
+ g_string_append (q, t);
+ p = s;
+ if (*s == '}')
+ p++;
+ }
+
+ state = copy_sym;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ g_free (path_tilde);
+
+ return q;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* CDPATH handling */
+static gboolean
+handle_cdpath (const char *path)
+{
+ gboolean result = FALSE;
+
+ /* CDPATH handling */
+ if (!IS_PATH_SEP (*path))
+ {
+ char *cdpath, *p;
+ char c;
+
+ cdpath = g_strdup (getenv ("CDPATH"));
+ p = cdpath;
+ c = (p == NULL) ? '\0' : ':';
+
+ while (!result && c == ':')
+ {
+ char *s;
+
+ s = strchr (p, ':');
+ if (s == NULL)
+ s = strchr (p, '\0');
+ c = *s;
+ *s = '\0';
+ if (*p != '\0')
+ {
+ vfs_path_t *r_vpath;
+
+ r_vpath = vfs_path_build_filename (p, path, (char *) NULL);
+ result = panel_cd (current_panel, r_vpath, cd_parse_command);
+ vfs_path_free (r_vpath, TRUE);
+ }
+ *s = c;
+ p = s + 1;
+ }
+ g_free (cdpath);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Execute the cd command to specified path
+ *
+ * @param path path to cd
+ */
+
+void
+cd_to (const char *path)
+{
+ char *p;
+
+ /* Remove leading whitespaces. */
+ /* Any final whitespace should be removed here (to see why, try "cd fred "). */
+ /* NOTE: I think we should not remove the extra space,
+ that way, we can cd into hidden directories */
+ /* FIXME: what about interpreting quoted strings like the shell.
+ so one could type "cd <tab> M-a <enter>" and it would work. */
+ p = g_strstrip (g_strdup (path));
+
+ if (get_current_type () == view_tree)
+ {
+ vfs_path_t *new_vpath = NULL;
+
+ if (p[0] == '\0')
+ {
+ new_vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ sync_tree (new_vpath);
+ }
+ else if (DIR_IS_DOTDOT (p))
+ {
+ if (vfs_path_elements_count (current_panel->cwd_vpath) != 1 ||
+ strlen (vfs_path_get_by_index (current_panel->cwd_vpath, 0)->path) > 1)
+ {
+ vfs_path_t *tmp_vpath = current_panel->cwd_vpath;
+
+ current_panel->cwd_vpath =
+ vfs_path_vtokens_get (tmp_vpath, 0, vfs_path_tokens_count (tmp_vpath) - 1);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ sync_tree (current_panel->cwd_vpath);
+ }
+ else
+ {
+ if (IS_PATH_SEP (*p))
+ new_vpath = vfs_path_from_str (p);
+ else
+ new_vpath = vfs_path_append_new (current_panel->cwd_vpath, p, (char *) NULL);
+
+ sync_tree (new_vpath);
+ }
+
+ vfs_path_free (new_vpath, TRUE);
+ }
+ else
+ {
+ GString *s_path;
+ vfs_path_t *q_vpath;
+ gboolean ok;
+
+ s_path = examine_cd (p);
+
+ if (s_path->len == 0)
+ q_vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ else
+ q_vpath = vfs_path_from_str_flags (s_path->str, VPF_NO_CANON);
+
+ ok = panel_cd (current_panel, q_vpath, cd_parse_command);
+ if (!ok)
+ ok = handle_cdpath (s_path->str);
+ if (!ok)
+ {
+ char *d;
+
+ d = vfs_path_to_str_flags (q_vpath, 0, VPF_STRIP_PASSWORD);
+ cd_error_message (d);
+ g_free (d);
+ }
+
+ vfs_path_free (q_vpath, TRUE);
+ g_string_free (s_path, TRUE);
+ }
+
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+cd_error_message (const char *path)
+{
+ message (D_ERROR, MSG_ERROR, _("Cannot change directory to\n%s\n%s"), path,
+ unix_error_string (errno));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cd.h b/src/filemanager/cd.h
new file mode 100644
index 0000000..13e7718
--- /dev/null
+++ b/src/filemanager/cd.h
@@ -0,0 +1,23 @@
+/** \file cd.h
+ * \brief Header: cd function
+ */
+
+#ifndef MC__CD_H
+#define MC__CD_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void cd_to (const char *path);
+void cd_error_message (const char *path);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__CD_H */
diff --git a/src/filemanager/chattr.c b/src/filemanager/chattr.c
new file mode 100644
index 0000000..08a5a99
--- /dev/null
+++ b/src/filemanager/chattr.c
@@ -0,0 +1,1358 @@
+/*
+ Chattr command -- for the Midnight Commander
+
+ Copyright (C) 2020-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file chattr.c
+ * \brief Source: chattr command
+ */
+
+/* TODO: change attributes recursively (ticket #3109) */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ext2fs/ext2_fs.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* tty_print*() */
+#include "lib/tty/color.h" /* tty_setcolor() */
+#include "lib/skin.h" /* COLOR_NORMAL, DISABLED_COLOR */
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* x_basename() */
+
+#include "src/keymap.h" /* chattr_map */
+
+#include "cmd.h" /* chattr_cmd(), chattr_get_as_str() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_MARKED B_USER
+#define B_SETALL (B_USER + 1)
+#define B_SETMRK (B_USER + 2)
+#define B_CLRMRK (B_USER + 3)
+
+#define BUTTONS 6
+
+#define CHATTRBOXES(x) ((WChattrBoxes *)(x))
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct WFileAttrText WFileAttrText;
+
+struct WFileAttrText
+{
+ Widget widget; /* base class */
+
+ char *filename;
+ int filename_width; /* cached width of file name */
+ char attrs[32 + 1]; /* 32 bits in attributes (unsigned long) */
+};
+
+typedef struct WChattrBoxes WChattrBoxes;
+
+struct WChattrBoxes
+{
+ WGroup base; /* base class */
+
+ int pos; /* The current checkbox selected */
+ int top; /* The first flag displayed */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* see /usr/include/ext2fs/ext2_fs.h
+ *
+ * EXT2_SECRM_FL 0x00000001 -- Secure deletion
+ * EXT2_UNRM_FL 0x00000002 -- Undelete
+ * EXT2_COMPR_FL 0x00000004 -- Compress file
+ * EXT2_SYNC_FL 0x00000008 -- Synchronous updates
+ * EXT2_IMMUTABLE_FL 0x00000010 -- Immutable file
+ * EXT2_APPEND_FL 0x00000020 -- writes to file may only append
+ * EXT2_NODUMP_FL 0x00000040 -- do not dump file
+ * EXT2_NOATIME_FL 0x00000080 -- do not update atime
+ * * Reserved for compression usage...
+ * EXT2_DIRTY_FL 0x00000100
+ * EXT2_COMPRBLK_FL 0x00000200 -- One or more compressed clusters
+ * EXT2_NOCOMPR_FL 0x00000400 -- Access raw compressed data
+ * * nb: was previously EXT2_ECOMPR_FL
+ * EXT4_ENCRYPT_FL 0x00000800 -- encrypted inode
+ * * End compression flags --- maybe not all used
+ * EXT2_BTREE_FL 0x00001000 -- btree format dir
+ * EXT2_INDEX_FL 0x00001000 -- hash-indexed directory
+ * EXT2_IMAGIC_FL 0x00002000
+ * EXT3_JOURNAL_DATA_FL 0x00004000 -- file data should be journaled
+ * EXT2_NOTAIL_FL 0x00008000 -- file tail should not be merged
+ * EXT2_DIRSYNC_FL 0x00010000 -- Synchronous directory modifications
+ * EXT2_TOPDIR_FL 0x00020000 -- Top of directory hierarchies
+ * EXT4_HUGE_FILE_FL 0x00040000 -- Set to each huge file
+ * EXT4_EXTENTS_FL 0x00080000 -- Inode uses extents
+ * EXT4_VERITY_FL 0x00100000 -- Verity protected inode
+ * EXT4_EA_INODE_FL 0x00200000 -- Inode used for large EA
+ * EXT4_EOFBLOCKS_FL 0x00400000 was here, unused
+ * FS_NOCOW_FL 0x00800000 -- Do not cow file
+ * EXT4_SNAPFILE_FL 0x01000000 -- Inode is a snapshot
+ * FS_DAX_FL 0x02000000 -- Inode is DAX
+ * EXT4_SNAPFILE_DELETED_FL 0x04000000 -- Snapshot is being deleted
+ * EXT4_SNAPFILE_SHRUNK_FL 0x08000000 -- Snapshot shrink has completed
+ * EXT4_INLINE_DATA_FL 0x10000000 -- Inode has inline data
+ * EXT4_PROJINHERIT_FL 0x20000000 -- Create with parents projid
+ * EXT4_CASEFOLD_FL 0x40000000 -- Casefolded file
+ * 0x80000000 -- unused yet
+ */
+
+static struct
+{
+ unsigned long flags;
+ char attr;
+ const char *text;
+ gboolean selected;
+ gboolean state; /* state of checkboxes */
+} check_attr[] =
+{
+ /* *INDENT-OFF* */
+ { EXT2_SECRM_FL, 's', N_("Secure deletion"), FALSE, FALSE },
+ { EXT2_UNRM_FL, 'u', N_("Undelete"), FALSE, FALSE },
+ { EXT2_SYNC_FL, 'S', N_("Synchronous updates"), FALSE, FALSE },
+ { EXT2_DIRSYNC_FL, 'D', N_("Synchronous directory updates"), FALSE, FALSE },
+ { EXT2_IMMUTABLE_FL, 'i', N_("Immutable"), FALSE, FALSE },
+ { EXT2_APPEND_FL, 'a', N_("Append only"), FALSE, FALSE },
+ { EXT2_NODUMP_FL, 'd', N_("No dump"), FALSE, FALSE },
+ { EXT2_NOATIME_FL, 'A', N_("No update atime"), FALSE, FALSE },
+ { EXT2_COMPR_FL, 'c', N_("Compress"), FALSE, FALSE },
+#ifdef EXT2_COMPRBLK_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_COMPRBLK_FL, 'B', N_("Compressed clusters"), FALSE, FALSE },
+#endif
+#ifdef EXT2_DIRTY_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_DIRTY_FL, 'Z', N_("Compressed dirty file"), FALSE, FALSE },
+#endif
+#ifdef EXT2_NOCOMPR_FL
+ /* removed in v1.43-WIP-2015-05-18
+ ext2fsprogs 4a05268cf86f7138c78d80a53f7e162f32128a3d 2015-04-12 */
+ { EXT2_NOCOMPR_FL, 'X', N_("Compression raw access"), FALSE, FALSE },
+#endif
+#ifdef EXT4_ENCRYPT_FL
+ { EXT4_ENCRYPT_FL, 'E', N_("Encrypted inode"), FALSE, FALSE },
+#endif
+ { EXT3_JOURNAL_DATA_FL, 'j', N_("Journaled data"), FALSE, FALSE },
+ { EXT2_INDEX_FL, 'I', N_("Indexed directory"), FALSE, FALSE },
+ { EXT2_NOTAIL_FL, 't', N_("No tail merging"), FALSE, FALSE },
+ { EXT2_TOPDIR_FL, 'T', N_("Top of directory hierarchies"), FALSE, FALSE },
+ { EXT4_EXTENTS_FL, 'e', N_("Inode uses extents"), FALSE, FALSE },
+#ifdef EXT4_HUGE_FILE_FL
+ /* removed in v1.43.9
+ ext2fsprogs 4825daeb0228e556444d199274b08c499ac3706c 2018-02-06 */
+ { EXT4_HUGE_FILE_FL, 'h', N_("Huge_file"), FALSE, FALSE },
+#endif
+ { FS_NOCOW_FL, 'C', N_("No COW"), FALSE, FALSE },
+#ifdef FS_DAX_FL
+ /* added in v1.45.7
+ ext2fsprogs 1dd48bc23c3776df76459aff0c7723fff850ea45 2020-07-28 */
+ { FS_DAX_FL, 'x', N_("Direct access for files"), FALSE, FALSE },
+#endif
+#ifdef EXT4_CASEFOLD_FL
+ /* added in v1.45.0
+ ext2fsprogs 1378bb6515e98a27f0f5c220381d49d20544204e 2018-12-01 */
+ { EXT4_CASEFOLD_FL, 'F', N_("Casefolded file"), FALSE, FALSE },
+#endif
+#ifdef EXT4_INLINE_DATA_FL
+ { EXT4_INLINE_DATA_FL, 'N', N_("Inode has inline data"), FALSE, FALSE },
+#endif
+#ifdef EXT4_PROJINHERIT_FL
+ /* added in v1.43-WIP-2016-05-12
+ ext2fsprogs e1cec4464bdaf93ea609de43c5cdeb6a1f553483 2016-03-07
+ 97d7e2fdb2ebec70c3124c1a6370d28ec02efad0 2016-05-09 */
+ { EXT4_PROJINHERIT_FL, 'P', N_("Project hierarchy"), FALSE, FALSE },
+#endif
+#ifdef EXT4_VERITY_FL
+ /* added in v1.44.4
+ ext2fsprogs faae7aa00df0abe7c6151fc4947aa6501b981ee1 2018-08-14
+ v1.44.5
+ ext2fsprogs 7e5a95e3d59719361661086ec7188ca6e674f139 2018-08-21 */
+ { EXT4_VERITY_FL, 'V', N_("Verity protected inode"), FALSE, FALSE }
+#endif
+ /* *INDENT-ON* */
+};
+
+/* number of attributes */
+static const size_t check_attr_num = G_N_ELEMENTS (check_attr);
+
+/* modifiable attribute numbers */
+static int check_attr_mod[32];
+static int check_attr_mod_num = 0; /* 0..31 */
+
+/* maximum width of attribute text */
+static int check_attr_width = 0;
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int width;
+ const char *text;
+ Widget *button;
+} chattr_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ /* 0 */ { B_SETALL, NORMAL_BUTTON, 0, N_("Set &all"), NULL },
+ /* 1 */ { B_MARKED, NORMAL_BUTTON, 0, N_("&Marked all"), NULL },
+ /* 2 */ { B_SETMRK, NORMAL_BUTTON, 0, N_("S&et marked"), NULL },
+ /* 3 */ { B_CLRMRK, NORMAL_BUTTON, 0, N_("C&lear marked"), NULL },
+ /* 4 */ { B_ENTER, DEFPUSH_BUTTON, 0, N_("&Set"), NULL },
+ /* 5 */ { B_CANCEL, NORMAL_BUTTON, 0, N_("&Cancel"), NULL }
+ /* *INDENT-ON* */
+};
+
+static gboolean flags_changed;
+static int current_file;
+static gboolean ignore_all;
+
+static unsigned long and_mask, or_mask, flags;
+
+static WFileAttrText *file_attr;
+
+/* x-coord of widget in the dialog */
+static const int wx = 3;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+chattr_is_modifiable (size_t i)
+{
+ return ((check_attr[i].flags & EXT2_FL_USER_MODIFIABLE) != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_fill_str (unsigned long attr, char *str)
+{
+ size_t i;
+
+ for (i = 0; i < check_attr_num; i++)
+ str[i] = (attr & check_attr[i].flags) != 0 ? check_attr[i].attr : '-';
+
+ str[check_attr_num] = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fileattrtext_fill (WFileAttrText * fat, unsigned long attr)
+{
+ chattr_fill_str (attr, fat->attrs);
+ widget_draw (WIDGET (fat));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+fileattrtext_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WFileAttrText *fat = (WFileAttrText *) w;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ int color;
+ size_t i;
+
+ color = COLOR_NORMAL;
+ tty_setcolor (color);
+
+ if (w->rect.cols > fat->filename_width)
+ {
+ widget_gotoyx (w, 0, (w->rect.cols - fat->filename_width) / 2);
+ tty_print_string (fat->filename);
+ }
+ else
+ {
+ widget_gotoyx (w, 0, 0);
+ tty_print_string (str_trunc (fat->filename, w->rect.cols));
+ }
+
+ /* hope that w->cols is greater than check_attr_num */
+ widget_gotoyx (w, 1, (w->rect.cols - check_attr_num) / 2);
+ for (i = 0; i < check_attr_num; i++)
+ {
+ /* Do not set new color for each symbol. Try to use previous color. */
+ if (chattr_is_modifiable (i))
+ {
+ if (color == DISABLED_COLOR)
+ {
+ color = COLOR_NORMAL;
+ tty_setcolor (color);
+ }
+ }
+ else
+ {
+ if (color != DISABLED_COLOR)
+ {
+ color = DISABLED_COLOR;
+ tty_setcolor (color);
+ }
+ }
+
+ tty_print_char (fat->attrs[i]);
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_RESIZE:
+ {
+ const WRect *wo = &CONST_WIDGET (w->owner)->rect;
+
+ widget_default_callback (w, sender, msg, parm, data);
+ /* initially file name may be wider than screen */
+ if (fat->filename_width > wo->cols - wx * 2)
+ {
+ w->rect.x = wo->x + wx;
+ w->rect.cols = wo->cols - wx * 2;
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (fat->filename);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WFileAttrText *
+fileattrtext_new (int y, int x, const char *filename, unsigned long attr)
+{
+ WRect r = { y, x, 2, 1 };
+ WFileAttrText *fat;
+ int width;
+
+ width = str_term_width1 (filename);
+ r.cols = MAX (width, (int) check_attr_num);
+
+ fat = g_new (WFileAttrText, 1);
+ widget_init (WIDGET (fat), &r, fileattrtext_callback, NULL);
+
+ fat->filename = g_strdup (filename);
+ fat->filename_width = width;
+ fileattrtext_fill (fat, attr);
+
+ return fat;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_draw_select (const Widget * w, gboolean selected)
+{
+ widget_gotoyx (w, 0, -1);
+ tty_print_char (selected ? '*' : ' ');
+ widget_gotoyx (w, 0, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_toggle_select (const WChattrBoxes * cb, int Id)
+{
+ Widget *w;
+
+ /* find checkbox */
+ w = WIDGET (g_list_nth_data (CONST_GROUP (cb)->widgets, Id - cb->top));
+
+ check_attr[Id].selected = !check_attr[Id].selected;
+
+ tty_setcolor (COLOR_NORMAL);
+ chattr_draw_select (w, check_attr[Id].selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+chattrboxes_draw_scrollbar (const WChattrBoxes * cb)
+{
+ const Widget *w = CONST_WIDGET (cb);
+ int max_line;
+ int line;
+ int i;
+
+ /* Are we at the top? */
+ widget_gotoyx (w, 0, w->rect.cols);
+ if (cb->top == 0)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('^');
+
+ max_line = w->rect.lines - 1;
+
+ /* Are we at the bottom? */
+ widget_gotoyx (w, max_line, w->rect.cols);
+ if (cb->top + w->rect.lines == check_attr_mod_num || w->rect.lines >= check_attr_mod_num)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('v');
+
+ /* Now draw the nice relative pointer */
+ line = 1 + (cb->pos * (w->rect.lines - 2)) / check_attr_mod_num;
+
+ for (i = 1; i < max_line; i++)
+ {
+ widget_gotoyx (w, i, w->rect.cols);
+ if (i != line)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('*');
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_draw (WChattrBoxes * cb)
+{
+ Widget *w = WIDGET (cb);
+ int i;
+ GList *l;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_fill_region (w->rect.y, w->rect.x - 1, w->rect.lines, w->rect.cols + 1, ' ');
+
+ /* redraw checkboxes */
+ group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
+
+ /* draw scrollbar */
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ if (!mc_global.tty.slow_terminal && check_attr_mod_num > w->rect.lines)
+ chattrboxes_draw_scrollbar (cb);
+
+ /* mark selected checkboxes */
+ for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ chattr_draw_select (WIDGET (l->data), check_attr[i].selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_rename (WChattrBoxes * cb)
+{
+ Widget *w = WIDGET (cb);
+ gboolean active;
+ int i;
+ GList *l;
+ char btext[BUF_SMALL]; /* FIXME: is 128 bytes enough? */
+
+ active = widget_get_state (w, WST_ACTIVE);
+
+ /* lock the group to avoid redraw of checkboxes individually */
+ if (active)
+ widget_set_state (w, WST_SUSPENDED, TRUE);
+
+ for (i = cb->top, l = GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ {
+ WCheck *c = CHECK (l->data);
+ int m;
+
+ m = check_attr_mod[i];
+ g_snprintf (btext, sizeof (btext), "(%c) %s", check_attr[m].attr, check_attr[m].text);
+ check_set_text (c, btext);
+ c->state = check_attr[m].state;
+ }
+
+ /* unlock */
+ if (active)
+ widget_set_state (w, WST_ACTIVE, TRUE);
+
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+checkboxes_save_state (const WChattrBoxes * cb)
+{
+ int i;
+ GList *l;
+
+ for (i = cb->top, l = CONST_GROUP (cb)->widgets; l != NULL; i++, l = g_list_next (l))
+ {
+ int m;
+
+ m = check_attr_mod[i];
+ check_attr[m].state = CHECK (l->data)->state;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_down (WChattrBoxes * cb)
+{
+ if (cb->pos == cb->top + WIDGET (cb)->rect.lines - 1)
+ {
+ /* We are on the last checkbox.
+ Keep this position. */
+
+ if (cb->pos == check_attr_mod_num - 1)
+ /* get out of widget */
+ return MSG_NOT_HANDLED;
+
+ /* emulate scroll of checkboxes */
+ checkboxes_save_state (cb);
+ cb->pos++;
+ cb->top++;
+ chattrboxes_rename (cb);
+ }
+ else /* cb->pos > cb-top */
+ {
+ GList *l;
+
+ /* select next checkbox */
+ cb->pos++;
+ l = g_list_next (GROUP (cb)->current);
+ widget_select (WIDGET (l->data));
+ }
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_page_down (WChattrBoxes * cb)
+{
+ WGroup *g = GROUP (cb);
+ GList *l;
+
+ if (cb->pos == check_attr_mod_num - 1)
+ {
+ /* We are on the last checkbox.
+ Keep this position.
+ Do nothing. */
+ l = g_list_last (g->widgets);
+ }
+ else
+ {
+ int i = WIDGET (cb)->rect.lines;
+
+ checkboxes_save_state (cb);
+
+ if (cb->top > check_attr_mod_num - 2 * i)
+ i = check_attr_mod_num - i - cb->top;
+ if (cb->top + i < 0)
+ i = -cb->top;
+ if (i == 0)
+ {
+ cb->pos = check_attr_mod_num - 1;
+ cb->top += i;
+ l = g_list_last (g->widgets);
+ }
+ else
+ {
+ cb->pos += i;
+ cb->top += i;
+ l = g_list_nth (g->widgets, cb->pos - cb->top);
+ }
+
+ chattrboxes_rename (cb);
+ }
+
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_end (WChattrBoxes * cb)
+{
+ GList *l;
+
+ checkboxes_save_state (cb);
+ cb->pos = check_attr_mod_num - 1;
+ cb->top = cb->pos - WIDGET (cb)->rect.lines + 1;
+ l = g_list_last (GROUP (cb)->widgets);
+ chattrboxes_rename (cb);
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_up (WChattrBoxes * cb)
+{
+ if (cb->pos == cb->top)
+ {
+ /* We are on the first checkbox.
+ Keep this position. */
+
+ if (cb->top == 0)
+ /* get out of widget */
+ return MSG_NOT_HANDLED;
+
+ /* emulate scroll of checkboxes */
+ checkboxes_save_state (cb);
+ cb->pos--;
+ cb->top--;
+ chattrboxes_rename (cb);
+ }
+ else /* cb->pos > cb-top */
+ {
+ GList *l;
+
+ /* select previous checkbox */
+ cb->pos--;
+ l = g_list_previous (GROUP (cb)->current);
+ widget_select (WIDGET (l->data));
+ }
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_page_up (WChattrBoxes * cb)
+{
+ WGroup *g = GROUP (cb);
+ GList *l;
+
+ if (cb->pos == 0 && cb->top == 0)
+ {
+ /* We are on the first checkbox.
+ Keep this position.
+ Do nothing. */
+ l = g_list_first (g->widgets);
+ }
+ else
+ {
+ int i = WIDGET (cb)->rect.lines;
+
+ checkboxes_save_state (cb);
+
+ if (cb->top < i)
+ i = cb->top;
+ if (i == 0)
+ {
+ cb->pos = 0;
+ cb->top -= i;
+ l = g_list_first (g->widgets);
+ }
+ else
+ {
+ cb->pos -= i;
+ cb->top -= i;
+ l = g_list_nth (g->widgets, cb->pos - cb->top);
+ }
+
+ chattrboxes_rename (cb);
+ }
+
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_home (WChattrBoxes * cb)
+{
+ GList *l;
+
+ checkboxes_save_state (cb);
+ cb->pos = 0;
+ cb->top = 0;
+ l = g_list_first (GROUP (cb)->widgets);
+ chattrboxes_rename (cb);
+ widget_select (WIDGET (l->data));
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_execute_cmd (WChattrBoxes * cb, long command)
+{
+ switch (command)
+ {
+ case CK_Down:
+ return chattrboxes_down (cb);
+
+ case CK_PageDown:
+ return chattrboxes_page_down (cb);
+
+ case CK_Bottom:
+ return chattrboxes_end (cb);
+
+ case CK_Up:
+ return chattrboxes_up (cb);
+
+ case CK_PageUp:
+ return chattrboxes_page_up (cb);
+
+ case CK_Top:
+ return chattrboxes_home (cb);
+
+ case CK_Mark:
+ case CK_MarkAndDown:
+ {
+ chattr_toggle_select (cb, cb->pos); /* FIXME */
+ if (command == CK_MarkAndDown)
+ chattrboxes_down (cb);
+
+ return MSG_HANDLED;
+ }
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_key (WChattrBoxes * cb, int key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (cb), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return chattrboxes_execute_cmd (cb, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chattrboxes_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WChattrBoxes *cb = CHATTRBOXES (w);
+ WGroup *g = GROUP (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ chattrboxes_draw (cb);
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ {
+ /* handle checkboxes */
+ int i;
+
+ i = g_list_index (g->widgets, sender);
+ if (i >= 0)
+ {
+ int m;
+
+ i += cb->top;
+ m = check_attr_mod[i];
+ flags ^= check_attr[m].flags;
+ fileattrtext_fill (file_attr, flags);
+ chattr_toggle_select (cb, i);
+ flags_changed = TRUE;
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_CHANGED_FOCUS:
+ /* sender is one of chattr checkboxes */
+ if (widget_get_state (sender, WST_FOCUSED))
+ {
+ int i;
+
+ i = g_list_index (g->widgets, sender);
+ cb->pos = cb->top + i;
+ }
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ {
+ cb_ret_t ret;
+
+ ret = chattrboxes_key (cb, parm);
+ if (ret != MSG_HANDLED)
+ ret = group_default_callback (w, NULL, MSG_KEY, parm, NULL);
+
+ return ret;
+ }
+
+ case MSG_ACTION:
+ return chattrboxes_execute_cmd (cb, parm);
+
+ case MSG_DESTROY:
+ /* save all states */
+ checkboxes_save_state (cb);
+ MC_FALLTHROUGH;
+
+ default:
+ return group_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+chattrboxes_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ int mou;
+
+ mou = mouse_handle_event (w, event);
+ if (mou == MOU_UNHANDLED)
+ mou = group_handle_mouse_event (w, event);
+
+ return mou;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattrboxes_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WChattrBoxes *cb = CHATTRBOXES (w);
+
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_SCROLL_UP:
+ chattrboxes_up (cb);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ chattrboxes_down (cb);
+ break;
+
+ default:
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WChattrBoxes *
+chattrboxes_new (const WRect * r)
+{
+ WChattrBoxes *cb;
+ Widget *w;
+ WGroup *cbg;
+ int i;
+
+ cb = g_new0 (WChattrBoxes, 1);
+ w = WIDGET (cb);
+ cbg = GROUP (cb);
+ group_init (cbg, r, chattrboxes_callback, chattrboxes_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR;
+ w->mouse_handler = chattrboxes_handle_mouse_event;
+ w->keymap = chattr_map;
+
+ /* create checkboxes */
+ for (i = 0; i < r->lines; i++)
+ {
+ int m;
+ WCheck *check;
+
+ m = check_attr_mod[i];
+
+ check = check_new (i, 0, check_attr[m].state, NULL);
+ group_add_widget (cbg, check);
+ }
+
+ chattrboxes_rename (cb);
+
+ /* select first checkbox */
+ cbg->current = cbg->widgets;
+
+ return cb;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_init (void)
+{
+ static gboolean i18n = FALSE;
+ size_t i;
+
+ for (i = 0; i < check_attr_num; i++)
+ check_attr[i].selected = FALSE;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i))
+ {
+ int width;
+
+#ifdef ENABLE_NLS
+ check_attr[i].text = _(check_attr[i].text);
+#endif
+
+ check_attr_mod[check_attr_mod_num++] = i;
+
+ width = 4 + str_term_width1 (check_attr[i].text); /* "(Q) text " */
+ check_attr_width = MAX (check_attr_width, width);
+ }
+
+ check_attr_width += 1 + 3 + 1; /* mark, [x] and space */
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+#ifdef ENABLE_NLS
+ chattr_but[i].text = _(chattr_but[i].text);
+#endif
+
+ chattr_but[i].width = str_term_width1 (chattr_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chattr_but[i].flags == DEFPUSH_BUTTON)
+ chattr_but[i].width += 2; /* <> */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chattr_dlg_create (WPanel * panel, const char *fname, unsigned long attr)
+{
+ Widget *mw = WIDGET (WIDGET (panel)->owner);
+ gboolean single_set;
+ WDialog *ch_dlg;
+ int lines, cols;
+ int checkboxes_lines = check_attr_mod_num;
+ size_t i;
+ int y;
+ Widget *dw;
+ WGroup *dg;
+ WChattrBoxes *cb;
+ const int cb_scrollbar_width = 1;
+ WRect r;
+
+ /* prepare to set up checkbox states */
+ for (i = 0; i < check_attr_num; i++)
+ check_attr[i].state = chattr_is_modifiable (i) && (attr & check_attr[i].flags) != 0;
+
+ cols = check_attr_width + cb_scrollbar_width;
+
+ single_set = (panel->marked < 2);
+
+ lines = 5 + checkboxes_lines + 4;
+ if (!single_set)
+ lines += 3;
+
+ if (lines >= mw->rect.lines - 2)
+ {
+ int dl;
+
+ dl = lines - (mw->rect.lines - 2);
+ lines -= dl;
+ checkboxes_lines -= dl;
+ }
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols + wx * 2, WPOS_CENTER, FALSE, dialog_colors,
+ dlg_default_callback, NULL, "[Chattr]", _("Chattr command"));
+ dg = GROUP (ch_dlg);
+ dw = WIDGET (ch_dlg);
+
+ y = 2;
+ file_attr = fileattrtext_new (y, wx, fname, attr);
+ group_add_widget_autopos (dg, file_attr, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ y += WIDGET (file_attr)->rect.lines;
+ group_add_widget (dg, hline_new (y++, -1, -1));
+
+ if (cols < WIDGET (file_attr)->rect.cols)
+ {
+ r = dw->rect;
+ cols = WIDGET (file_attr)->rect.cols;
+ cols = MIN (cols, mw->rect.cols - wx * 2);
+ r.cols = cols + wx * 2;
+ r.lines = lines;
+ widget_set_size_rect (dw, &r);
+ }
+
+ checkboxes_lines = MIN (check_attr_mod_num, checkboxes_lines);
+ rect_init (&r, y++, wx, checkboxes_lines > 0 ? checkboxes_lines : 1, cols);
+ cb = chattrboxes_new (&r);
+ group_add_widget_autopos (dg, cb, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+
+ y += checkboxes_lines - 1;
+ cols = 0;
+
+ for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
+ {
+ if (i == 0 || i == BUTTONS - 2)
+ group_add_widget (dg, hline_new (y++, -1, -1));
+
+ chattr_but[i].button = WIDGET (button_new (y, dw->rect.cols / 2 + 1 - chattr_but[i].width,
+ chattr_but[i].ret_cmd, chattr_but[i].flags,
+ chattr_but[i].text, NULL));
+ group_add_widget (dg, chattr_but[i].button);
+
+ i++;
+ chattr_but[i].button =
+ WIDGET (button_new (y++, dw->rect.cols / 2 + 2, chattr_but[i].ret_cmd,
+ chattr_but[i].flags, chattr_but[i].text, NULL));
+ group_add_widget (dg, chattr_but[i].button);
+
+ /* two buttons in a row */
+ cols =
+ MAX (cols, chattr_but[i - 1].button->rect.cols + 1 + chattr_but[i].button->rect.cols);
+ }
+
+ /* adjust dialog size and button positions */
+ cols += 6;
+ if (cols > dw->rect.cols)
+ {
+ r = dw->rect;
+ r.lines = lines;
+ r.cols = cols;
+ widget_set_size_rect (dw, &r);
+
+ /* dialog center */
+ cols = dw->rect.x + dw->rect.cols / 2 + 1;
+
+ for (i = single_set ? (BUTTONS - 2) : 0; i < BUTTONS; i++)
+ {
+ Widget *b;
+
+ b = chattr_but[i++].button;
+ r = b->rect;
+ r.x = cols - r.cols;
+ widget_set_size_rect (b, &r);
+
+ b = chattr_but[i].button;
+ r = b->rect;
+ r.x = cols + 1;
+ widget_set_size_rect (b, &r);
+ }
+ }
+
+ widget_select (WIDGET (cb));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chattr (const vfs_path_t * p, unsigned long m)
+{
+ const char *fname = NULL;
+
+ while (mc_fsetflags (p, m) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chattr \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chattr (WPanel * panel, const vfs_path_t * p, unsigned long m)
+{
+ gboolean ret;
+
+ m &= and_mask;
+ m |= or_mask;
+
+ ret = try_chattr (p, m);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chattr_apply_mask (WPanel * panel, vfs_path_t * vpath, unsigned long m)
+{
+ gboolean ok;
+
+ if (!do_chattr (panel, vpath, m))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_fgetflags (vpath, &m) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ flags = m;
+ ok = do_chattr (panel, vpath, m);
+ vfs_path_free (vpath, TRUE);
+ }
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chattr_cmd (WPanel * panel)
+{
+ gboolean need_update = FALSE;
+ gboolean end_chattr = FALSE;
+
+ chattr_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ const GString *fname;
+ size_t i;
+ int result;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chattr = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_fgetflags (vpath, &flags) != 0)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot get flags of \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ flags_changed = FALSE;
+
+ ch_dlg = chattr_dlg_create (panel, fname->str, flags);
+ result = dlg_run (ch_dlg);
+ widget_destroy (WIDGET (ch_dlg));
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chattr = TRUE;
+ break;
+
+ case B_ENTER:
+ if (flags_changed)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_fsetflags (vpath, flags) == -1 && !ignore_all)
+ message (D_ERROR, MSG_ERROR, _("Cannot chattr \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ end_chattr = TRUE;
+ }
+ else if (!try_chattr (vpath, flags))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chattr = TRUE;
+ }
+ }
+
+ need_update = TRUE;
+ break;
+
+ case B_SETALL:
+ case B_MARKED:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && (check_attr[i].selected || result == B_SETALL))
+ {
+ if (check_attr[i].state)
+ or_mask |= check_attr[i].flags;
+ else
+ and_mask &= ~check_attr[i].flags;
+ }
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ case B_SETMRK:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && check_attr[i].selected)
+ or_mask |= check_attr[i].flags;
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ case B_CLRMRK:
+ or_mask = 0;
+ and_mask = ~0;
+
+ for (i = 0; i < check_attr_num; i++)
+ if (chattr_is_modifiable (i) && check_attr[i].selected)
+ and_mask &= ~check_attr[i].flags;
+
+ chattr_apply_mask (panel, vpath, flags);
+ need_update = TRUE;
+ end_chattr = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ }
+ while (panel->marked != 0 && !end_chattr);
+
+ chattr_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+chattr_get_as_str (unsigned long attr)
+{
+ static char str[32 + 1]; /* 32 bits in attributes (unsigned long) */
+
+ chattr_fill_str (attr, str);
+
+ return str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/chmod.c b/src/filemanager/chmod.c
new file mode 100644
index 0000000..c93bcbc
--- /dev/null
+++ b/src/filemanager/chmod.c
@@ -0,0 +1,664 @@
+/*
+ Chmod command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file chmod.c
+ * \brief Source: chmod command
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "cmd.h" /* chmod_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define PX 3
+#define PY 2
+
+#define B_MARKED B_USER
+#define B_SETALL (B_USER + 1)
+#define B_SETMRK (B_USER + 2)
+#define B_CLRMRK (B_USER + 3)
+
+#define BUTTONS 6
+#define BUTTONS_PERM 12
+#define LABELS 4
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ mode_t mode;
+ const char *text;
+ gboolean selected;
+ WCheck *check;
+} check_perm[BUTTONS_PERM] =
+{
+ /* *INDENT-OFF* */
+ { S_ISUID, N_("set &user ID on execution"), FALSE, NULL },
+ { S_ISGID, N_("set &group ID on execution"), FALSE, NULL },
+ { S_ISVTX, N_("stick&y bit"), FALSE, NULL },
+ { S_IRUSR, N_("&read by owner"), FALSE, NULL },
+ { S_IWUSR, N_("&write by owner"), FALSE, NULL },
+ { S_IXUSR, N_("e&xecute/search by owner"), FALSE, NULL },
+ { S_IRGRP, N_("rea&d by group"), FALSE, NULL },
+ { S_IWGRP, N_("write by grou&p"), FALSE, NULL },
+ { S_IXGRP, N_("execu&te/search by group"), FALSE, NULL },
+ { S_IROTH, N_("read &by others"), FALSE, NULL },
+ { S_IWOTH, N_("wr&ite by others"), FALSE, NULL },
+ { S_IXOTH, N_("execute/searc&h by others"), FALSE, NULL }
+ /* *INDENT-ON* */
+};
+
+static int check_perm_len = 0;
+
+static const char *file_info_labels[LABELS] = {
+ N_("Name:"),
+ N_("Permissions (octal):"),
+ N_("Owner name:"),
+ N_("Group name:")
+};
+
+static int file_info_labels_len = 0;
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int y; /* vertical position relatively to dialog bottom boundary */
+ int len;
+ const char *text;
+} chmod_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { B_SETALL, NORMAL_BUTTON, 6, 0, N_("Set &all") },
+ { B_MARKED, NORMAL_BUTTON, 6, 0, N_("&Marked all") },
+ { B_SETMRK, NORMAL_BUTTON, 5, 0, N_("S&et marked") },
+ { B_CLRMRK, NORMAL_BUTTON, 5, 0, N_("C&lear marked") },
+ { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
+ { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+static gboolean mode_change;
+static int current_file;
+static gboolean ignore_all;
+
+static mode_t and_mask, or_mask, ch_mode;
+
+static WLabel *statl;
+static WGroupbox *file_gb;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i, len;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ check_perm[i].selected = FALSE;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+#ifdef ENABLE_NLS
+ for (i = 0; i < BUTTONS_PERM; i++)
+ check_perm[i].text = _(check_perm[i].text);
+
+ for (i = 0; i < LABELS; i++)
+ file_info_labels[i] = _(file_info_labels[i]);
+
+ for (i = 0; i < BUTTONS; i++)
+ chmod_but[i].text = _(chmod_but[i].text);
+#endif /* ENABLE_NLS */
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ {
+ len = str_term_width1 (check_perm[i].text);
+ check_perm_len = MAX (check_perm_len, len);
+ }
+
+ check_perm_len += 1 + 3 + 1; /* mark, [x] and space */
+
+ for (i = 0; i < LABELS; i++)
+ {
+ len = str_term_width1 (file_info_labels[i]) + 2; /* spaces around */
+ file_info_labels_len = MAX (file_info_labels_len, len);
+ }
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+ chmod_but[i].len = str_term_width1 (chmod_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chmod_but[i].flags == DEFPUSH_BUTTON)
+ chmod_but[i].len += 2; /* <> */
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_draw_select (const WDialog * h, int Id)
+{
+ widget_gotoyx (h, PY + Id + 1, PX + 1);
+ tty_print_char (check_perm[Id].selected ? '*' : ' ');
+ widget_gotoyx (h, PY + Id + 1, PX + 3);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_toggle_select (const WDialog * h, int Id)
+{
+ check_perm[Id].selected = !check_perm[Id].selected;
+ tty_setcolor (COLOR_NORMAL);
+ chmod_draw_select (h, Id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_refresh (const WDialog * h)
+{
+ int i;
+ int y, x;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ chmod_draw_select (h, i);
+
+ y = WIDGET (file_gb)->rect.y + 1;
+ x = WIDGET (file_gb)->rect.x + 2;
+
+ tty_gotoyx (y, x);
+ tty_print_string (file_info_labels[0]);
+ tty_gotoyx (y + 2, x);
+ tty_print_string (file_info_labels[1]);
+ tty_gotoyx (y + 4, x);
+ tty_print_string (file_info_labels[2]);
+ tty_gotoyx (y + 6, x);
+ tty_print_string (file_info_labels[3]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chmod_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ chmod_refresh (CONST_DIALOG (w->owner));
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chmod_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_NOTIFY:
+ {
+ /* handle checkboxes */
+ int i;
+
+ /* whether notification was sent by checkbox? */
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (sender == WIDGET (check_perm[i].check))
+ break;
+
+ if (i < BUTTONS_PERM)
+ {
+ ch_mode ^= check_perm[i].mode;
+ label_set_textv (statl, "%o", (unsigned int) ch_mode);
+ chmod_toggle_select (h, i);
+ mode_change = TRUE;
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm == 'T' || parm == 't' || parm == KEY_IC)
+ {
+ int i;
+ unsigned long id;
+
+ id = group_get_current_widget_id (g);
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (id == WIDGET (check_perm[i].check)->id)
+ break;
+
+ if (i < BUTTONS_PERM)
+ {
+ chmod_toggle_select (h, i);
+ if (parm == KEY_IC)
+ group_select_next_widget (g);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chmod_dlg_create (WPanel * panel, const char *fname, const struct stat *sf_stat)
+{
+ gboolean single_set;
+ WDialog *ch_dlg;
+ WGroup *g;
+ int lines, cols;
+ int i, y;
+ int perm_gb_len;
+ int file_gb_len;
+ const char *c_fname, *c_fown, *c_fgrp;
+ char buffer[BUF_TINY];
+
+ mode_change = FALSE;
+
+ single_set = (panel->marked < 2);
+ perm_gb_len = check_perm_len + 2;
+ file_gb_len = file_info_labels_len + 2;
+ cols = str_term_width1 (fname) + 2 + 1;
+ file_gb_len = MAX (file_gb_len, cols);
+
+ lines = single_set ? 20 : 23;
+ cols = perm_gb_len + file_gb_len + 1 + 6;
+
+ if (cols > COLS)
+ {
+ /* shrink the right groupbox */
+ cols = COLS;
+ file_gb_len = cols - (perm_gb_len + 1 + 6);
+ }
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors,
+ chmod_callback, NULL, "[Chmod]", _("Chmod command"));
+ g = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = chmod_bg_callback;
+
+ group_add_widget (g, groupbox_new (PY, PX, BUTTONS_PERM + 2, perm_gb_len, _("Permission")));
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ {
+ check_perm[i].check = check_new (PY + i + 1, PX + 2, (ch_mode & check_perm[i].mode) != 0,
+ check_perm[i].text);
+ group_add_widget (g, check_perm[i].check);
+ }
+
+ file_gb = groupbox_new (PY, PX + perm_gb_len + 1, BUTTONS_PERM + 2, file_gb_len, _("File"));
+ group_add_widget (g, file_gb);
+
+ /* Set the labels */
+ y = PY + 2;
+ cols = PX + perm_gb_len + 3;
+ c_fname = str_trunc (fname, file_gb_len - 3);
+ group_add_widget (g, label_new (y, cols, c_fname));
+ g_snprintf (buffer, sizeof (buffer), "%o", (unsigned int) ch_mode);
+ statl = label_new (y + 2, cols, buffer);
+ group_add_widget (g, statl);
+ c_fown = str_trunc (get_owner (sf_stat->st_uid), file_gb_len - 3);
+ group_add_widget (g, label_new (y + 4, cols, c_fown));
+ c_fgrp = str_trunc (get_group (sf_stat->st_gid), file_gb_len - 3);
+ group_add_widget (g, label_new (y + 6, cols, c_fgrp));
+
+ if (!single_set)
+ {
+ i = 0;
+
+ group_add_widget (g, hline_new (lines - chmod_but[i].y - 1, -1, -1));
+
+ for (; i < BUTTONS - 2; i++)
+ {
+ y = lines - chmod_but[i].y;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
+ chmod_but[i].ret_cmd, chmod_but[i].flags,
+ chmod_but[i].text, NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1,
+ chmod_but[i].ret_cmd, chmod_but[i].flags,
+ chmod_but[i].text, NULL));
+ }
+ }
+
+ i = BUTTONS - 2;
+ y = lines - chmod_but[i].y;
+ group_add_widget (g, hline_new (y - 1, -1, -1));
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chmod_but[i].len,
+ chmod_but[i].ret_cmd, chmod_but[i].flags, chmod_but[i].text,
+ NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chmod_but[i].ret_cmd,
+ chmod_but[i].flags, chmod_but[i].text, NULL));
+
+ /* select first checkbox */
+ widget_select (WIDGET (check_perm[0].check));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chmod_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chmod (const vfs_path_t * p, mode_t m)
+{
+ const char *fname = NULL;
+
+ while (mc_chmod (p, m) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chmod \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chmod (WPanel * panel, const vfs_path_t * p, struct stat *sf)
+{
+ gboolean ret;
+
+ sf->st_mode &= and_mask;
+ sf->st_mode |= or_mask;
+
+ ret = try_chmod (p, sf->st_mode);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_mask (WPanel * panel, vfs_path_t * vpath, struct stat *sf)
+{
+ gboolean ok;
+
+ if (!do_chmod (panel, vpath, sf))
+ return;
+
+ do
+ {
+ const GString *fname;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ {
+ ch_mode = sf->st_mode;
+
+ ok = do_chmod (panel, vpath, sf);
+ }
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chmod_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chmod;
+
+ chmod_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ struct stat sf_stat;
+ const GString *fname;
+ int i, result;
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chmod = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_mode = sf_stat.st_mode;
+
+ ch_dlg = chmod_dlg_create (panel, fname->str, &sf_stat);
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chmod = TRUE;
+ break;
+
+ case B_ENTER:
+ if (mode_change)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chmod (vpath, ch_mode) == -1 && !ignore_all)
+ message (D_ERROR, MSG_ERROR, _("Cannot chmod \"%s\"\n%s"), fname->str,
+ unix_error_string (errno));
+ end_chmod = TRUE;
+ }
+ else if (!try_chmod (vpath, ch_mode))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chmod = TRUE;
+ }
+ }
+
+ need_update = TRUE;
+ break;
+
+ case B_SETALL:
+ case B_MARKED:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected || result == B_SETALL)
+ {
+ if (check_perm[i].check->state)
+ or_mask |= check_perm[i].mode;
+ else
+ and_mask &= ~check_perm[i].mode;
+ }
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ case B_SETMRK:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected)
+ or_mask |= check_perm[i].mode;
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ case B_CLRMRK:
+ and_mask = or_mask = 0;
+ and_mask = ~and_mask;
+
+ for (i = 0; i < BUTTONS_PERM; i++)
+ if (check_perm[i].selected)
+ and_mask &= ~check_perm[i].mode;
+
+ apply_mask (panel, vpath, &sf_stat);
+ need_update = TRUE;
+ end_chmod = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chmod);
+
+ chmod_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/chown.c b/src/filemanager/chown.c
new file mode 100644
index 0000000..1ce769f
--- /dev/null
+++ b/src/filemanager/chown.c
@@ -0,0 +1,552 @@
+/*
+ Chown command -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file chown.c
+ * \brief Source: chown command
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "cmd.h" /* chown_cmd() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define GH 12
+#define GW 21
+
+#define BUTTONS 5
+
+#define B_SETALL B_USER
+#define B_SETUSR (B_USER + 1)
+#define B_SETGRP (B_USER + 2)
+
+#define LABELS 5
+
+#define chown_label(n,txt) label_set_text (chown_label [n].l, txt)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ int y;
+ int len;
+ const char *text;
+} chown_but[BUTTONS] =
+{
+ /* *INDENT-OFF* */
+ { B_SETALL, NORMAL_BUTTON, 5, 0, N_("Set &all") },
+ { B_SETGRP, NORMAL_BUTTON, 5, 0, N_("Set &groups") },
+ { B_SETUSR, NORMAL_BUTTON, 5, 0, N_("Set &users") },
+ { B_ENTER, DEFPUSH_BUTTON, 3, 0, N_("&Set") },
+ { B_CANCEL, NORMAL_BUTTON, 3, 0, N_("&Cancel") }
+ /* *INDENT-ON* */
+};
+
+/* summary length of three buttons */
+static int blen = 0;
+
+static struct
+{
+ int y;
+ WLabel *l;
+} chown_label[LABELS] =
+{
+ /* *INDENT-OFF* */
+ { 4, NULL },
+ { 6, NULL },
+ { 8, NULL },
+ { 10, NULL },
+ { 12, NULL }
+ /* *INDENT-ON* */
+};
+
+static int current_file;
+static gboolean ignore_all;
+
+static WListbox *l_user, *l_group;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_init (void)
+{
+ static gboolean i18n = FALSE;
+ int i;
+
+ if (i18n)
+ return;
+
+ i18n = TRUE;
+
+#ifdef ENABLE_NLS
+ for (i = 0; i < BUTTONS; i++)
+ chown_but[i].text = _(chown_but[i].text);
+#endif /* ENABLE_NLS */
+
+ for (i = 0; i < BUTTONS; i++)
+ {
+ chown_but[i].len = str_term_width1 (chown_but[i].text) + 3; /* [], spaces and w/o & */
+ if (chown_but[i].flags == DEFPUSH_BUTTON)
+ chown_but[i].len += 2; /* <> */
+
+ if (i < BUTTONS - 2)
+ blen += chown_but[i].len;
+ }
+
+ blen += 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_refresh (const Widget * h)
+{
+ int y = 3;
+ int x = 7 + GW * 2;
+
+ tty_setcolor (COLOR_NORMAL);
+
+ widget_gotoyx (h, y + 0, x);
+ tty_print_string (_("Name"));
+ widget_gotoyx (h, y + 2, x);
+ tty_print_string (_("Owner name"));
+ widget_gotoyx (h, y + 4, x);
+ tty_print_string (_("Group name"));
+ widget_gotoyx (h, y + 6, x);
+ tty_print_string (_("Size"));
+ widget_gotoyx (h, y + 8, x);
+ tty_print_string (_("Permission"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+chown_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+ chown_refresh (WIDGET (w->owner));
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+chown_dlg_create (WPanel * panel)
+{
+ int single_set;
+ WDialog *ch_dlg;
+ WGroup *g;
+ int lines, cols;
+ int i, y;
+ struct passwd *l_pass;
+ struct group *l_grp;
+
+ single_set = (panel->marked < 2) ? 3 : 0;
+ lines = GH + 4 + (single_set != 0 ? 2 : 4);
+ cols = GW * 3 + 2 + 6;
+
+ ch_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, NULL, NULL,
+ "[Chown]", _("Chown command"));
+ g = GROUP (ch_dlg);
+
+ /* draw background */
+ ch_dlg->bg->callback = chown_bg_callback;
+
+ group_add_widget (g, groupbox_new (2, 3, GH, GW, _("User name")));
+ l_user = listbox_new (3, 4, GH - 2, GW - 2, FALSE, NULL);
+ group_add_widget (g, l_user);
+ /* add field for unknown names (numbers) */
+ listbox_add_item (l_user, LISTBOX_APPEND_AT_END, 0, _("<Unknown user>"), NULL, FALSE);
+ /* get and put user names in the listbox */
+ setpwent ();
+ while ((l_pass = getpwent ()) != NULL)
+ listbox_add_item (l_user, LISTBOX_APPEND_SORTED, 0, l_pass->pw_name, NULL, FALSE);
+ endpwent ();
+
+ group_add_widget (g, groupbox_new (2, 4 + GW, GH, GW, _("Group name")));
+ l_group = listbox_new (3, 5 + GW, GH - 2, GW - 2, FALSE, NULL);
+ group_add_widget (g, l_group);
+ /* add field for unknown names (numbers) */
+ listbox_add_item (l_group, LISTBOX_APPEND_AT_END, 0, _("<Unknown group>"), NULL, FALSE);
+ /* get and put group names in the listbox */
+ setgrent ();
+ while ((l_grp = getgrent ()) != NULL)
+ listbox_add_item (l_group, LISTBOX_APPEND_SORTED, 0, l_grp->gr_name, NULL, FALSE);
+ endgrent ();
+
+ group_add_widget (g, groupbox_new (2, 5 + GW * 2, GH, GW, _("File")));
+ /* add widgets for the file information */
+ for (i = 0; i < LABELS; i++)
+ {
+ chown_label[i].l = label_new (chown_label[i].y, 7 + GW * 2, NULL);
+ group_add_widget (g, chown_label[i].l);
+ }
+
+ if (single_set == 0)
+ {
+ int x;
+
+ group_add_widget (g, hline_new (lines - chown_but[0].y - 1, -1, -1));
+
+ y = lines - chown_but[0].y;
+ x = (cols - blen) / 2;
+
+ for (i = 0; i < BUTTONS - 2; i++)
+ {
+ group_add_widget (g, button_new (y, x, chown_but[i].ret_cmd, chown_but[i].flags,
+ chown_but[i].text, NULL));
+ x += chown_but[i].len + 1;
+ }
+ }
+
+ i = BUTTONS - 2;
+ y = lines - chown_but[i].y;
+ group_add_widget (g, hline_new (y - 1, -1, -1));
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 - chown_but[i].len,
+ chown_but[i].ret_cmd, chown_but[i].flags, chown_but[i].text,
+ NULL));
+ i++;
+ group_add_widget (g, button_new (y, WIDGET (ch_dlg)->rect.cols / 2 + 1, chown_but[i].ret_cmd,
+ chown_but[i].flags, chown_but[i].text, NULL));
+
+ /* select first listbox */
+ widget_select (WIDGET (l_user));
+
+ return ch_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chown_done (gboolean need_update)
+{
+ if (need_update)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const GString *
+next_file (const WPanel * panel)
+{
+ while (panel->dir.list[current_file].f.marked == 0)
+ current_file++;
+
+ return panel->dir.list[current_file].fname;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_chown (const vfs_path_t * p, uid_t u, gid_t g)
+{
+ const char *fname = NULL;
+
+ while (mc_chown (p, u, g) == -1 && !ignore_all)
+ {
+ int my_errno = errno;
+ int result;
+ char *msg;
+
+ if (fname == NULL)
+ fname = x_basename (vfs_path_as_str (p));
+ msg = g_strdup_printf (_("Cannot chown \"%s\"\n%s"), fname, unix_error_string (my_errno));
+ result =
+ query_dialog (MSG_ERROR, msg, D_ERROR, 4, _("&Ignore"), _("Ignore &all"), _("&Retry"),
+ _("&Cancel"));
+ g_free (msg);
+
+ switch (result)
+ {
+ case 0:
+ /* try next file */
+ return TRUE;
+
+ case 1:
+ ignore_all = TRUE;
+ /* try next file */
+ return TRUE;
+
+ case 2:
+ /* retry this file */
+ break;
+
+ case 3:
+ default:
+ /* stop remain files processing */
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_chown (WPanel * panel, const vfs_path_t * p, uid_t u, gid_t g)
+{
+ gboolean ret;
+
+ ret = try_chown (p, u, g);
+
+ do_file_mark (panel, current_file, 0);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+apply_chowns (WPanel * panel, vfs_path_t * vpath, uid_t u, gid_t g)
+{
+ gboolean ok;
+
+ if (!do_chown (panel, vpath, u, g))
+ return;
+
+ do
+ {
+ const GString *fname;
+ struct stat sf;
+
+ fname = next_file (panel);
+ vpath = vfs_path_from_str (fname->str);
+ ok = (mc_stat (vpath, &sf) == 0);
+
+ if (!ok)
+ {
+ /* if current file was deleted outside mc -- try next file */
+ /* decrease panel->marked */
+ do_file_mark (panel, current_file, 0);
+
+ /* try next file */
+ ok = TRUE;
+ }
+ else
+ ok = do_chown (panel, vpath, u, g);
+
+ vfs_path_free (vpath, TRUE);
+ }
+ while (ok && panel->marked != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+chown_cmd (WPanel * panel)
+{
+ gboolean need_update;
+ gboolean end_chown;
+
+ chown_init ();
+
+ current_file = 0;
+ ignore_all = FALSE;
+
+ do
+ { /* do while any files remaining */
+ vfs_path_t *vpath;
+ WDialog *ch_dlg;
+ struct stat sf_stat;
+ const GString *fname;
+ int result;
+ char buffer[BUF_TINY];
+ uid_t new_user = (uid_t) (-1);
+ gid_t new_group = (gid_t) (-1);
+
+ do_refresh ();
+
+ need_update = FALSE;
+ end_chown = FALSE;
+
+ if (panel->marked != 0)
+ fname = next_file (panel); /* next marked file */
+ else
+ fname = panel_current_entry (panel)->fname; /* single file */
+
+ vpath = vfs_path_from_str (fname->str);
+
+ if (mc_stat (vpath, &sf_stat) != 0)
+ {
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+
+ ch_dlg = chown_dlg_create (panel);
+
+ /* select in listboxes */
+ listbox_set_current (l_user, listbox_search_text (l_user, get_owner (sf_stat.st_uid)));
+ listbox_set_current (l_group, listbox_search_text (l_group, get_group (sf_stat.st_gid)));
+
+ chown_label (0, str_trunc (fname->str, GW - 4));
+ chown_label (1, str_trunc (get_owner (sf_stat.st_uid), GW - 4));
+ chown_label (2, str_trunc (get_group (sf_stat.st_gid), GW - 4));
+ size_trunc_len (buffer, GW - 4, sf_stat.st_size, 0, panels_options.kilobyte_si);
+ chown_label (3, buffer);
+ chown_label (4, string_perm (sf_stat.st_mode));
+
+ result = dlg_run (ch_dlg);
+
+ switch (result)
+ {
+ case B_CANCEL:
+ end_chown = TRUE;
+ break;
+
+ case B_ENTER:
+ case B_SETALL:
+ {
+ struct group *grp;
+ struct passwd *user;
+ char *text;
+
+ listbox_get_current (l_group, &text, NULL);
+ grp = getgrnam (text);
+ if (grp != NULL)
+ new_group = grp->gr_gid;
+ listbox_get_current (l_user, &text, NULL);
+ user = getpwnam (text);
+ if (user != NULL)
+ new_user = user->pw_uid;
+ if (result == B_ENTER)
+ {
+ if (panel->marked <= 1)
+ {
+ /* single or last file */
+ if (mc_chown (vpath, new_user, new_group) == -1)
+ message (D_ERROR, MSG_ERROR, _("Cannot chown \"%s\"\n%s"),
+ fname->str, unix_error_string (errno));
+ end_chown = TRUE;
+ }
+ else if (!try_chown (vpath, new_user, new_group))
+ {
+ /* stop multiple files processing */
+ result = B_CANCEL;
+ end_chown = TRUE;
+ }
+ }
+ else
+ {
+ apply_chowns (panel, vpath, new_user, new_group);
+ end_chown = TRUE;
+ }
+
+ need_update = TRUE;
+ break;
+ }
+
+ case B_SETUSR:
+ {
+ struct passwd *user;
+ char *text;
+
+ listbox_get_current (l_user, &text, NULL);
+ user = getpwnam (text);
+ if (user != NULL)
+ {
+ new_user = user->pw_uid;
+ apply_chowns (panel, vpath, new_user, new_group);
+ need_update = TRUE;
+ end_chown = TRUE;
+ }
+ break;
+ }
+
+ case B_SETGRP:
+ {
+ struct group *grp;
+ char *text;
+
+ listbox_get_current (l_group, &text, NULL);
+ grp = getgrnam (text);
+ if (grp != NULL)
+ {
+ new_group = grp->gr_gid;
+ apply_chowns (panel, vpath, new_user, new_group);
+ need_update = TRUE;
+ end_chown = TRUE;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (panel->marked != 0 && result != B_CANCEL)
+ {
+ do_file_mark (panel, current_file, 0);
+ need_update = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ widget_destroy (WIDGET (ch_dlg));
+ }
+ while (panel->marked != 0 && !end_chown);
+
+ chown_done (need_update);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cmd.c b/src/filemanager/cmd.c
new file mode 100644
index 0000000..8c33fd8
--- /dev/null
+++ b/src/filemanager/cmd.c
@@ -0,0 +1,1470 @@
+/*
+ Routines invoked by a function key
+ They normally operate on the current panel.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file cmd.c
+ * \brief Source: routines invoked by a function key
+ *
+ * They normally operate on the current panel.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+#ifdef ENABLE_VFS_NET
+#include <netdb.h>
+#endif
+#include <unistd.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, tty_touch_screen() */
+#include "lib/tty/key.h" /* ALT() macro */
+#include "lib/mcconfig.h"
+#include "lib/filehighlight.h" /* MC_FHL_INI_FILE */
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/file-entry.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/keybind.h" /* CK_Down, CK_History */
+#include "lib/event.h" /* mc_event_raise() */
+
+#include "src/setup.h"
+#include "src/execute.h" /* toggle_panels() */
+#include "src/history.h"
+#include "src/usermenu.h" /* MC_GLOBAL_MENU */
+#include "src/util.h" /* check_for_default() */
+
+#include "src/viewer/mcviewer.h"
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h"
+#endif
+
+#ifdef USE_DIFF_VIEW
+#include "src/diffviewer/ydiff.h"
+#endif
+
+#include "fileopctx.h"
+#include "filenot.h"
+#include "hotlist.h" /* hotlist_show() */
+#include "tree.h" /* tree_chdir() */
+#include "filemanager.h" /* change_panel() */
+#include "command.h" /* cmdline */
+#include "layout.h" /* get_current_type() */
+#include "ext.h" /* regex_command() */
+#include "boxes.h" /* cd_box() */
+#include "dir.h"
+#include "cd.h"
+
+#include "cmd.h" /* Our definitions */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef HAVE_MMAP
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+#endif /* HAVE_MMAP */
+
+/*** file scope type declarations ****************************************************************/
+
+enum CompareMode
+{
+ compare_quick = 0,
+ compare_size_only,
+ compare_thourough
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef ENABLE_VFS_NET
+static const char *machine_str = N_("Enter machine name (F1 for details):");
+#endif /* ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run viewer (internal or external) on the current file.
+ * If @plain_view is TRUE, force internal viewer and raw mode (used for F13).
+ */
+static void
+do_view_cmd (WPanel * panel, gboolean plain_view)
+{
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ /* Directories are viewed by changing to them */
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *fname_vpath;
+
+ if (confirm_view_dir && (panel->marked != 0 || panel->dirs_marked != 0) &&
+ query_dialog (_("Confirmation"), _("Files tagged, want to cd?"), D_NORMAL, 2,
+ _("&Yes"), _("&No")) != 0)
+ return;
+
+ fname_vpath = vfs_path_from_str (fe->fname->str);
+ if (!panel_cd (panel, fname_vpath, cd_exact))
+ cd_error_message (fe->fname->str);
+ vfs_path_free (fname_vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *filename_vpath;
+
+ filename_vpath = vfs_path_from_str (fe->fname->str);
+ view_file (filename_vpath, plain_view, use_internal_view);
+ vfs_path_free (filename_vpath, TRUE);
+ }
+
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+do_edit (const vfs_path_t * what_vpath)
+{
+ edit_file_at_line (what_vpath, use_internal_edit, 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+compare_files (const vfs_path_t * vpath1, const vfs_path_t * vpath2, off_t size)
+{
+ int file1;
+ int result = -1; /* Different by default */
+
+ if (size == 0)
+ return 0;
+
+ file1 = open (vfs_path_as_str (vpath1), O_RDONLY);
+ if (file1 >= 0)
+ {
+ int file2;
+
+ file2 = open (vfs_path_as_str (vpath2), O_RDONLY);
+ if (file2 >= 0)
+ {
+#ifdef HAVE_MMAP
+ char *data1;
+
+ /* Ugly if jungle */
+ data1 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file1, 0);
+ if (data1 != (char *) -1)
+ {
+ char *data2;
+
+ data2 = mmap (0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, file2, 0);
+ if (data2 != (char *) -1)
+ {
+ rotate_dash (TRUE);
+ result = memcmp (data1, data2, size);
+ munmap (data2, size);
+ }
+ munmap (data1, size);
+ }
+#else
+ /* Don't have mmap() :( Even more ugly :) */
+ char buf1[BUFSIZ], buf2[BUFSIZ];
+ int n1, n2;
+
+ rotate_dash (TRUE);
+ do
+ {
+ while ((n1 = read (file1, buf1, sizeof (buf1))) == -1 && errno == EINTR)
+ ;
+ while ((n2 = read (file2, buf2, sizeof (buf2))) == -1 && errno == EINTR)
+ ;
+ }
+ while (n1 == n2 && n1 == sizeof (buf1) && memcmp (buf1, buf2, sizeof (buf1)) == 0);
+ result = (n1 != n2) || memcmp (buf1, buf2, n1);
+#endif /* !HAVE_MMAP */
+ close (file2);
+ }
+ close (file1);
+ }
+ rotate_dash (FALSE);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+compare_dir (WPanel * panel, const WPanel * other, enum CompareMode mode)
+{
+ int i, j;
+
+ /* No marks by default */
+ panel->marked = 0;
+ panel->total = 0;
+ panel->dirs_marked = 0;
+
+ /* Handle all files in the panel */
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ file_entry_t *source = &panel->dir.list[i];
+ const char *source_fname;
+
+ /* Default: unmarked */
+ file_mark (panel, i, 0);
+
+ /* Skip directories */
+ if (S_ISDIR (source->st.st_mode))
+ continue;
+
+ source_fname = source->fname->str;
+ if (panel->is_panelized)
+ source_fname = x_basename (source_fname);
+
+ /* Search the corresponding entry from the other panel */
+ for (j = 0; j < other->dir.len; j++)
+ {
+ const char *other_fname;
+
+ other_fname = other->dir.list[j].fname->str;
+ if (other->is_panelized)
+ other_fname = x_basename (other_fname);
+
+ if (strcmp (source_fname, other_fname) == 0)
+ break;
+ }
+
+ if (j >= other->dir.len)
+ /* Not found -> mark */
+ do_file_mark (panel, i, 1);
+ else
+ {
+ /* Found */
+ file_entry_t *target = &other->dir.list[j];
+
+ if (mode != compare_size_only)
+ /* Older version is not marked */
+ if (source->st.st_mtime < target->st.st_mtime)
+ continue;
+
+ /* Newer version with different size is marked */
+ if (source->st.st_size != target->st.st_size)
+ {
+ do_file_mark (panel, i, 1);
+ continue;
+ }
+
+ if (mode == compare_size_only)
+ continue;
+
+ if (mode == compare_quick)
+ {
+ /* Thorough compare off, compare only time stamps */
+ /* Mark newer version, don't mark version with the same date */
+ if (source->st.st_mtime > target->st.st_mtime)
+ do_file_mark (panel, i, 1);
+
+ continue;
+ }
+
+ /* Thorough compare on, do byte-by-byte comparison */
+ {
+ vfs_path_t *src_name, *dst_name;
+
+ src_name =
+ vfs_path_append_new (panel->cwd_vpath, source->fname->str, (char *) NULL);
+ dst_name =
+ vfs_path_append_new (other->cwd_vpath, target->fname->str, (char *) NULL);
+ if (compare_files (src_name, dst_name, source->st.st_size))
+ do_file_mark (panel, i, 1);
+ vfs_path_free (src_name, TRUE);
+ vfs_path_free (dst_name, TRUE);
+ }
+ }
+ } /* for (i ...) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_link (link_type_t link_type, const char *fname)
+{
+ char *dest = NULL, *src = NULL;
+ vfs_path_t *dest_vpath = NULL;
+
+ if (link_type == LINK_HARDLINK)
+ {
+ vfs_path_t *fname_vpath;
+
+ src = g_strdup_printf (_("Link %s to:"), str_trunc (fname, 46));
+ dest =
+ input_expand_dialog (_("Link"), src, MC_HISTORY_FM_LINK, "", INPUT_COMPLETE_FILENAMES);
+ if (dest == NULL || *dest == '\0')
+ goto cleanup;
+
+ save_cwds_stat ();
+
+ fname_vpath = vfs_path_from_str (fname);
+ dest_vpath = vfs_path_from_str (dest);
+ if (mc_link (fname_vpath, dest_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("link: %s"), unix_error_string (errno));
+ vfs_path_free (fname_vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *s, *d;
+
+ /* suggest the full path for symlink, and either the full or
+ relative path to the file it points to */
+ s = vfs_path_append_new (current_panel->cwd_vpath, fname, (char *) NULL);
+
+ if (get_other_type () == view_listing)
+ d = vfs_path_append_new (other_panel->cwd_vpath, fname, (char *) NULL);
+ else
+ d = vfs_path_from_str (fname);
+
+ if (link_type == LINK_SYMLINK_RELATIVE)
+ {
+ char *s_str;
+
+ s_str = diff_two_paths (other_panel->cwd_vpath, s);
+ vfs_path_free (s, TRUE);
+ s = vfs_path_from_str_flags (s_str, VPF_NO_CANON);
+ g_free (s_str);
+ }
+
+ symlink_box (s, d, &dest, &src);
+ vfs_path_free (d, TRUE);
+ vfs_path_free (s, TRUE);
+
+ if (dest == NULL || *dest == '\0' || src == NULL || *src == '\0')
+ goto cleanup;
+
+ save_cwds_stat ();
+
+ dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON);
+
+ s = vfs_path_from_str (src);
+ if (mc_symlink (dest_vpath, s) == -1)
+ message (D_ERROR, MSG_ERROR, _("symlink: %s"), unix_error_string (errno));
+ vfs_path_free (s, TRUE);
+ }
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+
+ cleanup:
+ vfs_path_free (dest_vpath, TRUE);
+ g_free (src);
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined(ENABLE_VFS_UNDELFS) || defined(ENABLE_VFS_NET)
+static void
+nice_cd (const char *text, const char *xtext, const char *help,
+ const char *history_name, const char *prefix, int to_home, gboolean strip_password)
+{
+ char *machine;
+ char *cd_path;
+
+ machine =
+ input_dialog_help (text, xtext, help, history_name, INPUT_LAST_TEXT, strip_password,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_HOSTNAMES |
+ INPUT_COMPLETE_USERNAMES);
+ if (machine == NULL)
+ return;
+
+ to_home = 0; /* FIXME: how to solve going to home nicely? /~/ is
+ ugly as hell and leads to problems in vfs layer */
+
+ if (strncmp (prefix, machine, strlen (prefix)) == 0)
+ cd_path = g_strconcat (machine, to_home ? "/~/" : (char *) NULL, (char *) NULL);
+ else
+ cd_path = g_strconcat (prefix, machine, to_home ? "/~/" : (char *) NULL, (char *) NULL);
+
+ g_free (machine);
+
+ if (!IS_PATH_SEP (*cd_path))
+ {
+ char *tmp = cd_path;
+
+ cd_path = g_strconcat (PATH_SEP_STR, tmp, (char *) NULL);
+ g_free (tmp);
+ }
+
+ {
+ panel_view_mode_t save_type;
+ vfs_path_t *cd_vpath;
+
+ save_type = get_panel_type (MENU_PANEL_IDX);
+
+ if (save_type != view_listing)
+ create_panel (MENU_PANEL_IDX, view_listing);
+
+ cd_vpath = vfs_path_from_str_flags (cd_path, VPF_NO_CANON);
+ if (!panel_do_cd (MENU_PANEL, cd_vpath, cd_parse_command))
+ {
+ cd_error_message (cd_path);
+
+ if (save_type != view_listing)
+ create_panel (MENU_PANEL_IDX, save_type);
+ }
+ vfs_path_free (cd_vpath, TRUE);
+ }
+ g_free (cd_path);
+
+ /* In case of passive panel, restore current VFS directory that was changed in panel_do_cd() */
+ if (MENU_PANEL != current_panel)
+ (void) mc_chdir (current_panel->cwd_vpath);
+}
+#endif /* ENABLE_VFS_UNDELFS || ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+configure_panel_listing (WPanel * p, int list_format, int brief_cols, gboolean use_msformat,
+ char **user, char **status)
+{
+ p->user_mini_status = use_msformat;
+ p->list_format = list_format;
+
+ if (list_format == list_brief)
+ p->brief_cols = brief_cols;
+
+ if (list_format == list_user || use_msformat)
+ {
+ g_free (p->user_format);
+ p->user_format = *user;
+ *user = NULL;
+
+ g_free (p->user_status_format[list_format]);
+ p->user_status_format[list_format] = *status;
+ *status = NULL;
+
+ set_panel_formats (p);
+ }
+
+ set_panel_formats (p);
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+switch_to_listing (int panel_index)
+{
+ if (get_panel_type (panel_index) != view_listing)
+ create_panel (panel_index, view_listing);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal,
+ long start_line, off_t search_start, off_t search_end)
+{
+ gboolean ret = TRUE;
+
+ if (plain_view)
+ {
+ mcview_mode_flags_t changed_flags;
+
+ mcview_clear_mode_flags (&changed_flags);
+ mcview_altered_flags.hex = FALSE;
+ mcview_altered_flags.magic = FALSE;
+ mcview_altered_flags.nroff = FALSE;
+ if (mcview_global_flags.hex)
+ changed_flags.hex = TRUE;
+ if (mcview_global_flags.magic)
+ changed_flags.magic = TRUE;
+ if (mcview_global_flags.nroff)
+ changed_flags.nroff = TRUE;
+ mcview_global_flags.hex = FALSE;
+ mcview_global_flags.magic = FALSE;
+ mcview_global_flags.nroff = FALSE;
+
+ ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end);
+
+ if (changed_flags.hex && !mcview_altered_flags.hex)
+ mcview_global_flags.hex = TRUE;
+ if (changed_flags.magic && !mcview_altered_flags.magic)
+ mcview_global_flags.magic = TRUE;
+ if (changed_flags.nroff && !mcview_altered_flags.nroff)
+ mcview_global_flags.nroff = TRUE;
+
+ dialog_switch_process_pending ();
+ }
+ else if (internal)
+ {
+ char view_entry[BUF_TINY];
+
+ if (start_line > 0)
+ g_snprintf (view_entry, sizeof (view_entry), "View:%ld", start_line);
+ else
+ strcpy (view_entry, "View");
+
+ ret = (regex_command (filename_vpath, view_entry) == 0);
+ if (ret)
+ {
+ ret = mcview_viewer (NULL, filename_vpath, start_line, search_start, search_end);
+ dialog_switch_process_pending ();
+ }
+ }
+ else
+ {
+ static const char *viewer = NULL;
+
+ if (viewer == NULL)
+ {
+ viewer = getenv ("VIEWER");
+ if (viewer == NULL)
+ viewer = getenv ("PAGER");
+ if (viewer == NULL)
+ viewer = "view";
+ }
+
+ execute_external_editor_or_viewer (viewer, filename_vpath, start_line);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** view_file (filename, plain_view, internal)
+ *
+ * Inputs:
+ * filename_vpath: The file name to view
+ * plain_view: If set does not do any fancy pre-processing (no filtering) and
+ * always invokes the internal viewer.
+ * internal: If set uses the internal viewer, otherwise an external viewer.
+ */
+
+gboolean
+view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal)
+{
+ return view_file_at_line (filename_vpath, plain_view, internal, 0, 0, 0);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/** Run user's preferred viewer on the current file */
+
+void
+view_cmd (WPanel * panel)
+{
+ do_view_cmd (panel, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Ask for file and run user's preferred viewer on it */
+
+void
+view_file_cmd (const WPanel * panel)
+{
+ char *filename;
+ vfs_path_t *vpath;
+
+ filename =
+ input_expand_dialog (_("View file"), _("Filename:"),
+ MC_HISTORY_FM_VIEW_FILE, panel_current_entry (panel)->fname->str,
+ INPUT_COMPLETE_FILENAMES);
+ if (filename == NULL)
+ return;
+
+ vpath = vfs_path_from_str (filename);
+ g_free (filename);
+ view_file (vpath, FALSE, use_internal_view);
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Run plain internal viewer on the current file */
+void
+view_raw_cmd (WPanel * panel)
+{
+ do_view_cmd (panel, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+view_filtered_cmd (const WPanel * panel)
+{
+ char *command;
+ const char *initial_command;
+
+ if (input_is_empty (cmdline))
+ initial_command = panel_current_entry (panel)->fname->str;
+ else
+ initial_command = input_get_ctext (cmdline);
+
+ command =
+ input_dialog (_("Filtered view"),
+ _("Filter command and arguments:"),
+ MC_HISTORY_FM_FILTERED_VIEW, initial_command,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
+
+ if (command != NULL)
+ {
+ mcview_viewer (command, NULL, 0, 0, 0);
+ g_free (command);
+ dialog_switch_process_pending ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line)
+{
+
+#ifdef USE_INTERNAL_EDIT
+ if (internal)
+ edit_file (what_vpath, start_line);
+ else
+#endif /* USE_INTERNAL_EDIT */
+ {
+ static const char *editor = NULL;
+
+ (void) internal;
+
+ if (editor == NULL)
+ {
+ editor = getenv ("EDITOR");
+ if (editor == NULL)
+ editor = get_default_editor ();
+ }
+
+ execute_external_editor_or_viewer (editor, what_vpath, start_line);
+ }
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+
+#ifdef USE_INTERNAL_EDIT
+ if (use_internal_edit)
+ dialog_switch_process_pending ();
+ else
+#endif /* USE_INTERNAL_EDIT */
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_cmd (const WPanel * panel)
+{
+ vfs_path_t *fname;
+
+ fname = vfs_path_from_str (panel_current_entry (panel)->fname->str);
+ if (regex_command (fname, "Edit") == 0)
+ do_edit (fname);
+ vfs_path_free (fname, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef USE_INTERNAL_EDIT
+void
+edit_cmd_force_internal (const WPanel * panel)
+{
+ vfs_path_t *fname;
+
+ fname = vfs_path_from_str (panel_current_entry (panel)->fname->str);
+ if (regex_command (fname, "Edit") == 0)
+ edit_file_at_line (fname, TRUE, 1);
+ vfs_path_free (fname, TRUE);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_cmd_new (void)
+{
+ vfs_path_t *fname_vpath = NULL;
+
+ if (editor_ask_filename_before_edit)
+ {
+ char *fname;
+
+ fname = input_expand_dialog (_("Edit file"), _("Enter file name:"),
+ MC_HISTORY_EDIT_LOAD, "", INPUT_COMPLETE_FILENAMES);
+ if (fname == NULL)
+ return;
+
+ if (*fname != '\0')
+ fname_vpath = vfs_path_from_str (fname);
+
+ g_free (fname);
+ }
+
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage = default_source_codepage;
+#endif
+ do_edit (fname_vpath);
+
+ vfs_path_free (fname_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mkdir_cmd (WPanel * panel)
+{
+ const file_entry_t *fe;
+ char *dir;
+ const char *name = "";
+
+ fe = panel_current_entry (panel);
+
+ /* If 'on' then automatically fills name with current item name */
+ if (auto_fill_mkdir_name && !DIR_IS_DOTDOT (fe->fname->str))
+ name = fe->fname->str;
+
+ dir =
+ input_expand_dialog (_("Create a new Directory"),
+ _("Enter directory name:"), MC_HISTORY_FM_MKDIR, name,
+ INPUT_COMPLETE_FILENAMES);
+
+ if (dir != NULL && *dir != '\0')
+ {
+ vfs_path_t *absdir;
+
+ if (IS_PATH_SEP (dir[0]) || dir[0] == '~')
+ absdir = vfs_path_from_str (dir);
+ else
+ {
+ /* possible escaped '~' */
+ /* allow create directory with name '~' */
+ char *tmpdir = dir;
+
+ if (dir[0] == '\\' && dir[1] == '~')
+ tmpdir = dir + 1;
+
+ absdir = vfs_path_append_new (panel->cwd_vpath, tmpdir, (char *) NULL);
+ }
+
+ save_cwds_stat ();
+
+ if (my_mkdir (absdir, 0777) != 0)
+ message (D_ERROR, MSG_ERROR, "%s", unix_error_string (errno));
+ else
+ {
+ update_panels (UP_OPTIMIZE, dir);
+ repaint_screen ();
+ select_item (panel);
+ }
+
+ vfs_path_free (absdir, TRUE);
+ }
+ g_free (dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+reread_cmd (void)
+{
+ panel_update_flags_t flag = UP_ONLY_CURRENT;
+
+ if (get_current_type () == view_listing && get_other_type () == view_listing &&
+ vfs_path_equal (current_panel->cwd_vpath, other_panel->cwd_vpath))
+ flag = UP_OPTIMIZE;
+
+ update_panels (UP_RELOAD | flag, UP_KEEPSEL);
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+ext_cmd (void)
+{
+ vfs_path_t *extdir_vpath;
+ int dir = 0;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Extension file edit"),
+ _("Which extension file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System Wide"));
+
+ extdir_vpath = vfs_path_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
+
+ if (dir == 0)
+ {
+ vfs_path_t *buffer_vpath;
+
+ buffer_vpath = mc_config_get_full_vpath (MC_EXT_FILE);
+ check_for_default (extdir_vpath, buffer_vpath);
+ do_edit (buffer_vpath);
+ vfs_path_free (buffer_vpath, TRUE);
+ }
+ else if (dir == 1)
+ {
+ if (!exist_file (vfs_path_get_last_path_str (extdir_vpath)))
+ {
+ vfs_path_free (extdir_vpath, TRUE);
+ extdir_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
+ }
+ do_edit (extdir_vpath);
+ }
+
+ vfs_path_free (extdir_vpath, TRUE);
+ flush_extension_file ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** edit file menu for mc */
+
+void
+edit_mc_menu_cmd (void)
+{
+ vfs_path_t *buffer_vpath;
+ vfs_path_t *menufile_vpath;
+ int dir = 0;
+
+ query_set_sel (1);
+ dir = query_dialog (_("Menu edit"),
+ _("Which menu file do you want to edit?"),
+ D_NORMAL, geteuid ()? 2 : 3, _("&Local"), _("&User"), _("&System Wide"));
+
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL);
+
+ if (!exist_file (vfs_path_get_last_path_str (menufile_vpath)))
+ {
+ vfs_path_free (menufile_vpath, TRUE);
+ menufile_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL);
+ }
+
+ switch (dir)
+ {
+ case 0:
+ buffer_vpath = vfs_path_from_str (MC_LOCAL_MENU);
+ check_for_default (menufile_vpath, buffer_vpath);
+ chmod (vfs_path_get_last_path_str (buffer_vpath), 0600);
+ break;
+
+ case 1:
+ buffer_vpath = mc_config_get_full_vpath (MC_USERMENU_FILE);
+ check_for_default (menufile_vpath, buffer_vpath);
+ break;
+
+ case 2:
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_GLOBAL_MENU, (char *) NULL);
+ if (!exist_file (vfs_path_get_last_path_str (buffer_vpath)))
+ {
+ vfs_path_free (buffer_vpath, TRUE);
+ buffer_vpath =
+ vfs_path_build_filename (mc_global.share_data_dir, MC_GLOBAL_MENU, (char *) NULL);
+ }
+ break;
+
+ default:
+ vfs_path_free (menufile_vpath, TRUE);
+ return;
+ }
+
+ do_edit (buffer_vpath);
+
+ vfs_path_free (buffer_vpath, TRUE);
+ vfs_path_free (menufile_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_fhl_cmd (void)
+{
+ vfs_path_t *fhlfile_vpath = NULL;
+ int dir = 0;
+
+ if (geteuid () == 0)
+ dir = query_dialog (_("Highlighting groups file edit"),
+ _("Which highlighting file you want to edit?"), D_NORMAL, 2,
+ _("&User"), _("&System Wide"));
+
+ fhlfile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL);
+
+ if (dir == 0)
+ {
+ vfs_path_t *buffer_vpath;
+
+ buffer_vpath = mc_config_get_full_vpath (MC_FHL_INI_FILE);
+ check_for_default (fhlfile_vpath, buffer_vpath);
+ do_edit (buffer_vpath);
+ vfs_path_free (buffer_vpath, TRUE);
+ }
+ else if (dir == 1)
+ {
+ if (!exist_file (vfs_path_get_last_path_str (fhlfile_vpath)))
+ {
+ vfs_path_free (fhlfile_vpath, TRUE);
+ fhlfile_vpath =
+ vfs_path_build_filename (mc_global.sysconfig_dir, MC_FHL_INI_FILE, (char *) NULL);
+ }
+ do_edit (fhlfile_vpath);
+ }
+
+ vfs_path_free (fhlfile_vpath, TRUE);
+ /* refresh highlighting rules */
+ mc_fhl_free (&mc_filehighlight);
+ mc_filehighlight = mc_fhl_new (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotlist_cmd (WPanel * panel)
+{
+ char *target;
+
+ target = hotlist_show (LIST_HOTLIST, panel);
+ if (target == NULL)
+ return;
+
+ if (get_current_type () == view_tree)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (target);
+ tree_chdir (the_tree, vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ else
+ {
+ vfs_path_t *deprecated_vpath;
+ const char *deprecated_path;
+
+ deprecated_vpath = vfs_path_from_str_flags (target, VPF_USE_DEPRECATED_PARSER);
+ deprecated_path = vfs_path_as_str (deprecated_vpath);
+ cd_to (deprecated_path);
+ vfs_path_free (deprecated_vpath, TRUE);
+ }
+
+ g_free (target);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+void
+vfs_list (WPanel * panel)
+{
+ char *target;
+ vfs_path_t *target_vpath;
+
+ target = hotlist_show (LIST_VFSLIST, panel);
+ if (target == NULL)
+ return;
+
+ target_vpath = vfs_path_from_str (target);
+ if (!panel_cd (current_panel, target_vpath, cd_exact))
+ cd_error_message (target);
+ vfs_path_free (target_vpath, TRUE);
+ g_free (target);
+}
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+compare_dirs_cmd (void)
+{
+ int choice;
+ enum CompareMode thorough_flag;
+
+ choice =
+ query_dialog (_("Compare directories"),
+ _("Select compare method:"), D_NORMAL, 4,
+ _("&Quick"), _("&Size only"), _("&Thorough"), _("&Cancel"));
+
+ if (choice < 0 || choice > 2)
+ return;
+
+ thorough_flag = choice;
+
+ if (get_current_type () == view_listing && get_other_type () == view_listing)
+ {
+ compare_dir (current_panel, other_panel, thorough_flag);
+ compare_dir (other_panel, current_panel, thorough_flag);
+ }
+ else
+ message (D_ERROR, MSG_ERROR,
+ _("Both panels should be in the listing mode\nto use this command"));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef USE_DIFF_VIEW
+void
+diff_view_cmd (void)
+{
+ /* both panels must be in the list mode */
+ if (get_current_type () == view_listing && get_other_type () == view_listing)
+ {
+ if (get_current_index () == 0)
+ dview_diff_cmd (current_panel, other_panel);
+ else
+ dview_diff_cmd (other_panel, current_panel);
+
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+
+ dialog_switch_process_pending ();
+ }
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+swap_cmd (void)
+{
+ swap_panels ();
+ tty_touch_screen ();
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+link_cmd (link_type_t link_type)
+{
+ const char *filename;
+
+ filename = panel_current_entry (current_panel)->fname->str;
+ if (filename != NULL)
+ do_link (link_type, filename);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+edit_symlink_cmd (void)
+{
+ const file_entry_t *fe;
+ const char *p;
+
+ fe = panel_current_entry (current_panel);
+ p = fe->fname->str;
+
+ if (!S_ISLNK (fe->st.st_mode))
+ message (D_ERROR, MSG_ERROR, _("'%s' is not a symbolic link"), p);
+ else
+ {
+ char buffer[MC_MAXPATHLEN];
+ int i;
+
+ i = readlink (p, buffer, sizeof (buffer) - 1);
+ if (i > 0)
+ {
+ char *q, *dest;
+
+ buffer[i] = '\0';
+
+ q = g_strdup_printf (_("Symlink '%s\' points to:"), str_trunc (p, 32));
+ dest =
+ input_expand_dialog (_("Edit symlink"), q, MC_HISTORY_FM_EDIT_LINK, buffer,
+ INPUT_COMPLETE_FILENAMES);
+ g_free (q);
+
+ if (dest != NULL && *dest != '\0' && strcmp (buffer, dest) != 0)
+ {
+ vfs_path_t *p_vpath;
+
+ p_vpath = vfs_path_from_str (p);
+
+ save_cwds_stat ();
+
+ if (mc_unlink (p_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("edit symlink, unable to remove %s: %s"), p,
+ unix_error_string (errno));
+ else
+ {
+ vfs_path_t *dest_vpath;
+
+ dest_vpath = vfs_path_from_str_flags (dest, VPF_NO_CANON);
+ if (mc_symlink (dest_vpath, p_vpath) == -1)
+ message (D_ERROR, MSG_ERROR, _("edit symlink: %s"),
+ unix_error_string (errno));
+ vfs_path_free (dest_vpath, TRUE);
+ }
+
+ vfs_path_free (p_vpath, TRUE);
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+ }
+
+ g_free (dest);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+help_cmd (void)
+{
+ ev_help_t event_data = { NULL, NULL };
+
+ if (current_panel->quick_search.active)
+ event_data.node = "[Quick search]";
+ else
+ event_data.node = "[main]";
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+user_file_menu_cmd (void)
+{
+ (void) user_menu_cmd (NULL, NULL, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_FTP
+void
+ftplink_cmd (void)
+{
+ nice_cd (_("FTP to machine"), _(machine_str),
+ "[FTP File System]", ":ftplink_cmd: FTP to machine ", "ftp://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_FTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_SFTP
+void
+sftplink_cmd (void)
+{
+ nice_cd (_("SFTP to machine"), _(machine_str),
+ "[SFTP (SSH File Transfer Protocol) filesystem]",
+ ":sftplink_cmd: SFTP to machine ", "sftp://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_SFTP */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_FISH
+void
+fishlink_cmd (void)
+{
+ nice_cd (_("Shell link to machine"), _(machine_str),
+ "[FIle transfer over SHell filesystem]", ":fishlink_cmd: Shell link to machine ",
+ "sh://", 1, TRUE);
+}
+#endif /* ENABLE_VFS_FISH */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS_UNDELFS
+void
+undelete_cmd (void)
+{
+ nice_cd (_("Undelete files on an ext2 file system"),
+ _("Enter device (without /dev/) to undelete\nfiles on: (F1 for details)"),
+ "[Undelete File System]", ":undelete_cmd: Undel on ext2 fs ", "undel://", 0, FALSE);
+}
+#endif /* ENABLE_VFS_UNDELFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_cd_cmd (WPanel * panel)
+{
+ char *p;
+
+ p = cd_box (panel);
+ if (p != NULL && *p != '\0')
+ cd_to (p);
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*!
+ \brief calculate dirs sizes
+
+ calculate dirs sizes and resort panel:
+ dirs_selected = show size for selected dirs,
+ otherwise = show size for dir under cursor:
+ dir under cursor ".." = show size for all dirs,
+ otherwise = show size for dir under cursor
+ */
+
+void
+smart_dirsize_cmd (WPanel * panel)
+{
+ const file_entry_t *entry;
+
+ entry = panel_current_entry (panel);
+ if ((S_ISDIR (entry->st.st_mode) && DIR_IS_DOTDOT (entry->fname->str)) || panel->dirs_marked)
+ dirsizes_cmd (panel);
+ else
+ single_dirsize_cmd (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+single_dirsize_cmd (WPanel * panel)
+{
+ file_entry_t *entry;
+
+ entry = panel_current_entry (panel);
+
+ if (S_ISDIR (entry->st.st_mode) && !DIR_IS_DOTDOT (entry->fname->str))
+ {
+ size_t dir_count = 0;
+ size_t count = 0;
+ uintmax_t total = 0;
+ dirsize_status_msg_t dsm;
+ vfs_path_t *p;
+
+ p = vfs_path_from_str (entry->fname->str);
+
+ memset (&dsm, 0, sizeof (dsm));
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ if (compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) == FILE_CONT)
+ {
+ entry->st.st_size = (off_t) total;
+ entry->f.dir_size_computed = 1;
+ }
+
+ vfs_path_free (p, TRUE);
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+ }
+
+ if (panels_options.mark_moves_down)
+ send_message (panel, NULL, MSG_ACTION, CK_Down, NULL);
+
+ recalculate_panel_summary (panel);
+
+ if (panel->sort_field->sort_routine == (GCompareFunc) sort_size)
+ panel_re_sort (panel);
+
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dirsizes_cmd (WPanel * panel)
+{
+ int i;
+ dirsize_status_msg_t dsm;
+
+ memset (&dsm, 0, sizeof (dsm));
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (S_ISDIR (panel->dir.list[i].st.st_mode)
+ && ((panel->dirs_marked != 0 && panel->dir.list[i].f.marked != 0)
+ || panel->dirs_marked == 0) && !DIR_IS_DOTDOT (panel->dir.list[i].fname->str))
+ {
+ vfs_path_t *p;
+ size_t dir_count = 0;
+ size_t count = 0;
+ uintmax_t total = 0;
+ gboolean ok;
+
+ p = vfs_path_from_str (panel->dir.list[i].fname->str);
+ ok = compute_dir_size (p, &dsm, &dir_count, &count, &total, FALSE) != FILE_CONT;
+ vfs_path_free (p, TRUE);
+ if (ok)
+ break;
+
+ panel->dir.list[i].st.st_size = (off_t) total;
+ panel->dir.list[i].f.dir_size_computed = 1;
+ }
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+
+ recalculate_panel_summary (panel);
+
+ if (panel->sort_field->sort_routine == (GCompareFunc) sort_size)
+ panel_re_sort (panel);
+
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+save_setup_cmd (void)
+{
+ vfs_path_t *vpath;
+ const char *path;
+
+ vpath = vfs_path_from_str_flags (mc_config_get_path (), VPF_STRIP_HOME);
+ path = vfs_path_as_str (vpath);
+
+ if (save_setup (TRUE, TRUE))
+ message (D_NORMAL, _("Setup"), _("Setup saved to %s"), path);
+ else
+ message (D_ERROR, _("Setup"), _("Unable to save setup to %s"), path);
+
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+info_cmd_no_menu (void)
+{
+ if (get_panel_type (0) == view_info)
+ create_panel (0, view_listing);
+ else if (get_panel_type (1) == view_info)
+ create_panel (1, view_listing);
+ else
+ create_panel (current_panel == left_panel ? 1 : 0, view_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_cmd_no_menu (void)
+{
+ if (get_panel_type (0) == view_quick)
+ create_panel (0, view_listing);
+ else if (get_panel_type (1) == view_quick)
+ create_panel (1, view_listing);
+ else
+ create_panel (current_panel == left_panel ? 1 : 0, view_quick);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listing_cmd (void)
+{
+ WPanel *p;
+
+ switch_to_listing (MENU_PANEL_IDX);
+
+ p = PANEL (get_panel_widget (MENU_PANEL_IDX));
+
+ p->is_panelized = FALSE;
+ panel_set_filter (p, NULL); /* including panel reload */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_listing_format_cmd (void)
+{
+ int list_format;
+ gboolean use_msformat;
+ int brief_cols;
+ char *user, *status;
+ WPanel *p = NULL;
+
+ if (SELECTED_IS_PANEL)
+ p = MENU_PANEL_IDX == 0 ? left_panel : right_panel;
+
+ list_format = panel_listing_box (p, MENU_PANEL_IDX, &user, &status, &use_msformat, &brief_cols);
+ if (list_format != -1)
+ {
+ switch_to_listing (MENU_PANEL_IDX);
+ p = MENU_PANEL_IDX == 0 ? left_panel : right_panel;
+ configure_panel_listing (p, list_format, brief_cols, use_msformat, &user, &status);
+ g_free (user);
+ g_free (status);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_tree_cmd (void)
+{
+ create_panel (MENU_PANEL_IDX, view_tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+info_cmd (void)
+{
+ create_panel (MENU_PANEL_IDX, view_info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+quick_view_cmd (void)
+{
+ if (PANEL (get_panel_widget (MENU_PANEL_IDX)) == current_panel)
+ (void) change_panel ();
+ create_panel (MENU_PANEL_IDX, view_quick);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+void
+encoding_cmd (void)
+{
+ if (SELECTED_IS_PANEL)
+ panel_change_encoding (MENU_PANEL);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/cmd.h b/src/filemanager/cmd.h
new file mode 100644
index 0000000..26bfdb7
--- /dev/null
+++ b/src/filemanager/cmd.h
@@ -0,0 +1,172 @@
+/** \file cmd.h
+ * \brief Header: routines invoked by a function key
+ *
+ * They normally operate on the current panel.
+ */
+
+#ifndef MC__CMD_H
+#define MC__CMD_H
+
+#include "lib/global.h"
+
+#include "file.h" /* panel_operate() */
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LINK_HARDLINK = 0,
+ LINK_SYMLINK_ABSOLUTE,
+ LINK_SYMLINK_RELATIVE
+} link_type_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+#ifdef ENABLE_VFS_FTP
+void ftplink_cmd (void);
+#endif
+#ifdef ENABLE_VFS_SFTP
+void sftplink_cmd (void);
+#endif
+#ifdef ENABLE_VFS_FISH
+void fishlink_cmd (void);
+#endif
+void undelete_cmd (void);
+void help_cmd (void);
+void smart_dirsize_cmd (WPanel * panel);
+void single_dirsize_cmd (WPanel * panel);
+void dirsizes_cmd (WPanel * panel);
+gboolean view_file_at_line (const vfs_path_t * filename_vpath, gboolean plain_view,
+ gboolean internal, long start_line, off_t search_start,
+ off_t search_end);
+gboolean view_file (const vfs_path_t * filename_vpath, gboolean plain_view, gboolean internal);
+void view_cmd (WPanel * panel);
+void view_file_cmd (const WPanel * panel);
+void view_raw_cmd (WPanel * panel);
+void view_filtered_cmd (const WPanel * panel);
+void edit_file_at_line (const vfs_path_t * what_vpath, gboolean internal, long start_line);
+void edit_cmd (const WPanel * panel);
+void edit_cmd_new (void);
+#ifdef USE_INTERNAL_EDIT
+void edit_cmd_force_internal (const WPanel * panel);
+#endif
+void mkdir_cmd (WPanel * panel);
+void reread_cmd (void);
+void vfs_list (WPanel * panel);
+void ext_cmd (void);
+void edit_mc_menu_cmd (void);
+void edit_fhl_cmd (void);
+void hotlist_cmd (WPanel * panel);
+void compare_dirs_cmd (void);
+#ifdef USE_DIFF_VIEW
+void diff_view_cmd (void);
+#endif
+void panel_tree_cmd (void);
+void link_cmd (link_type_t link_type);
+void edit_symlink_cmd (void);
+void swap_cmd (void);
+void quick_cd_cmd (WPanel * panel);
+void save_setup_cmd (void);
+void user_file_menu_cmd (void);
+void info_cmd (void);
+void listing_cmd (void);
+void setup_listing_format_cmd (void);
+void quick_cmd_no_menu (void);
+void info_cmd_no_menu (void);
+void quick_view_cmd (void);
+#ifdef HAVE_CHARSET
+void encoding_cmd (void);
+#endif
+/* achown.c */
+void advanced_chown_cmd (WPanel * panel);
+/* chmod.c */
+void chmod_cmd (WPanel * panel);
+/* chown.c */
+void chown_cmd (WPanel * panel);
+#ifdef ENABLE_EXT2FS_ATTR
+/* chattr.c */
+void chattr_cmd (WPanel * panel);
+const char *chattr_get_as_str (unsigned long attr);
+#endif
+/* find.c */
+void find_cmd (WPanel * panel);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Copy, default to the other panel.
+ */
+
+static inline void
+copy_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_COPY, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Copy, default to the same panel, ignore marks.
+ */
+
+static inline void
+copy_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_COPY, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move/rename, default to the other panel.
+ */
+
+static inline void
+rename_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_MOVE, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Move/rename, default to the same panel, ignore marks.
+ */
+
+static inline void
+rename_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_MOVE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove.
+ */
+
+static inline void
+delete_cmd (WPanel * panel)
+{
+ panel_operate (panel, OP_DELETE, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Remove, ignore marks.
+ */
+
+static inline void
+delete_cmd_local (WPanel * panel)
+{
+ panel_operate (panel, OP_DELETE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__CMD_H */
diff --git a/src/filemanager/command.c b/src/filemanager/command.c
new file mode 100644
index 0000000..47d2d75
--- /dev/null
+++ b/src/filemanager/command.c
@@ -0,0 +1,255 @@
+/*
+ Command line widget.
+ This widget is derived from the WInput widget, it's used to cope
+ with all the magic of the command input line, we depend on some
+ help from the program's callback.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file command.c
+ * \brief Source: command line widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h" /* vfs_current_is_local() */
+#include "lib/skin.h" /* DEFAULT_COLOR */
+#include "lib/util.h" /* whitespace() */
+#include "lib/widget.h"
+
+#include "src/setup.h" /* quit */
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+#include "src/execute.h" /* shell_execute() */
+#include "src/usermenu.h" /* expand_format() */
+
+#include "filemanager.h" /* quiet_quit_cmd(), layout.h */
+#include "cd.h" /* cd_to() */
+
+#include "command.h"
+
+/*** global variables ****************************************************************************/
+
+/* This holds the command line */
+WInput *cmdline;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Color styles command line */
+static input_colors_t command_colors;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Handle Enter on the command line
+ *
+ * @param lc_cmdline string for handling
+ * @return MSG_HANDLED on success else MSG_NOT_HANDLED
+ */
+
+static cb_ret_t
+enter (WInput * lc_cmdline)
+{
+ const char *cmd;
+
+ if (!command_prompt)
+ return MSG_HANDLED;
+
+ cmd = input_get_ctext (lc_cmdline);
+
+ /* Any initial whitespace should be removed at this point */
+ while (whiteness (*cmd))
+ cmd++;
+
+ if (*cmd == '\0')
+ return MSG_HANDLED;
+
+ if (strncmp (cmd, "cd", 2) == 0 && (cmd[2] == '\0' || whitespace (cmd[2])))
+ {
+ cd_to (cmd + 2);
+ input_clean (lc_cmdline);
+ return MSG_HANDLED;
+ }
+ else if (strcmp (cmd, "exit") == 0)
+ {
+ input_assign_text (lc_cmdline, "");
+ if (!quiet_quit_cmd ())
+ return MSG_NOT_HANDLED;
+ }
+ else
+ {
+ GString *command;
+ size_t i;
+
+ if (!vfs_current_is_local ())
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot execute commands on non-local filesystems"));
+ return MSG_NOT_HANDLED;
+ }
+#ifdef ENABLE_SUBSHELL
+ /* Check this early before we clean command line
+ * (will be checked again by shell_execute) */
+ if (mc_global.tty.use_subshell && subshell_state != INACTIVE)
+ {
+ message (D_ERROR, MSG_ERROR, _("The shell is already running a command"));
+ return MSG_NOT_HANDLED;
+ }
+#endif
+ command = g_string_sized_new (32);
+
+ for (i = 0; cmd[i] != '\0'; i++)
+ {
+ if (cmd[i] != '%')
+ g_string_append_c (command, cmd[i]);
+ else
+ {
+ char *s;
+
+ s = expand_format (NULL, cmd[++i], TRUE);
+ g_string_append (command, s);
+ g_free (s);
+ }
+ }
+
+ input_clean (lc_cmdline);
+ shell_execute (command->str, 0);
+ g_string_free (command, TRUE);
+
+#ifdef ENABLE_SUBSHELL
+ if ((quit & SUBSHELL_EXIT) != 0)
+ {
+ if (quiet_quit_cmd ())
+ return MSG_HANDLED;
+
+ quit = 0;
+ /* restart subshell */
+ if (mc_global.tty.use_subshell)
+ init_subshell ();
+ }
+
+ if (mc_global.tty.use_subshell)
+ do_load_prompt ();
+#endif
+ }
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default command line callback
+ *
+ * @param w Widget object
+ * @param msg message for handling
+ * @param parm extra parameter such as key code
+ *
+ * @return MSG_NOT_HANDLED on fail else MSG_HANDLED
+ */
+
+static cb_ret_t
+command_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_KEY:
+ /* Special case: we handle the enter key */
+ if (parm == '\n')
+ return enter (INPUT (w));
+ MC_FALLTHROUGH;
+
+ default:
+ return input_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WInput *
+command_new (int y, int x, int cols)
+{
+ WInput *cmd;
+ Widget *w;
+
+ cmd = input_new (y, x, command_colors, cols, "", "cmdline",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES
+ | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_CD | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_SHELL_ESC);
+ w = WIDGET (cmd);
+ /* Don't set WOP_SELECTABLE up, otherwise panels will be unselected */
+ widget_set_options (w, WOP_SELECTABLE, FALSE);
+ /* Add our hooks */
+ w->callback = command_callback;
+
+ return cmd;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set colors for the command line.
+ */
+
+void
+command_set_default_colors (void)
+{
+ command_colors[WINPUTC_MAIN] = DEFAULT_COLOR;
+ command_colors[WINPUTC_MARK] = COMMAND_MARK_COLOR;
+ command_colors[WINPUTC_UNCHANGED] = DEFAULT_COLOR;
+ command_colors[WINPUTC_HISTORY] = COMMAND_HISTORY_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Insert quoted text in input line. The function is meant for the
+ * command line, so the percent sign is quoted as well.
+ *
+ * @param in WInput object
+ * @param text string for insertion
+ * @param insert_extra_space add extra space
+ */
+
+void
+command_insert (WInput * in, const char *text, gboolean insert_extra_space)
+{
+ char *quoted_text;
+
+ quoted_text = name_quote (text, TRUE);
+ input_insert (in, quoted_text, insert_extra_space);
+ g_free (quoted_text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/command.h b/src/filemanager/command.h
new file mode 100644
index 0000000..9aacca4
--- /dev/null
+++ b/src/filemanager/command.h
@@ -0,0 +1,27 @@
+/** \file command.h
+ * \brief Header: command line widget
+ */
+
+#ifndef MC__COMMAND_H
+#define MC__COMMAND_H
+
+#include "lib/widget.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WInput *cmdline;
+
+/*** declarations of public functions ************************************************************/
+
+WInput *command_new (int y, int x, int len);
+void command_set_default_colors (void);
+void command_insert (WInput * in, const char *text, gboolean insert_extra_space);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__COMMAND_H */
diff --git a/src/filemanager/dir.c b/src/filemanager/dir.c
new file mode 100644
index 0000000..0931819
--- /dev/null
+++ b/src/filemanager/dir.c
@@ -0,0 +1,839 @@
+/*
+ Directory routines
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file src/filemanager/dir.c
+ * \brief Source: directory routines
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fs.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "treestore.h"
+#include "file.h" /* file_is_symlink_to_dir() */
+#include "dir.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MY_ISDIR(x) (\
+ (is_exe (x->st.st_mode) && !(S_ISDIR (x->st.st_mode) || link_isdir (x)) && exec_first) \
+ ? 1 \
+ : ( (S_ISDIR (x->st.st_mode) || link_isdir (x)) ? 2 : 0) )
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Reverse flag */
+static int reverse = 1;
+
+/* Are the files sorted case sensitively? */
+static gboolean case_sensitive = OS_SORT_CASE_SENSITIVE_DEFAULT;
+
+/* Are the exec_bit files top in list */
+static gboolean exec_first = TRUE;
+
+static dir_list dir_copy = { NULL, 0, 0, NULL };
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+key_collate (const char *t1, const char *t2)
+{
+ int dotdot = 0;
+ int ret;
+
+ dotdot = (t1[0] == '.' ? 1 : 0) | ((t2[0] == '.' ? 1 : 0) << 1);
+
+ switch (dotdot)
+ {
+ case 0:
+ case 3:
+ ret = str_key_collate (t1, t2, case_sensitive) * reverse;
+ break;
+ case 1:
+ ret = -1; /* t1 < t2 */
+ break;
+ case 2:
+ ret = 1; /* t1 > t2 */
+ break;
+ default:
+ ret = 0; /* it must not happen */
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline int
+compare_by_names (file_entry_t * a, file_entry_t * b)
+{
+ /* create key if does not exist, key will be freed after sorting */
+ if (a->name_sort_key == NULL)
+ a->name_sort_key = str_create_key_for_filename (a->fname->str, case_sensitive);
+ if (b->name_sort_key == NULL)
+ b->name_sort_key = str_create_key_for_filename (b->fname->str, case_sensitive);
+
+ return key_collate (a->name_sort_key, b->name_sort_key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * clear keys, should be call after sorting is finished.
+ */
+
+static void
+clean_sort_keys (dir_list * list, int start, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i + start];
+ str_release_key (fentry->name_sort_key, case_sensitive);
+ fentry->name_sort_key = NULL;
+ str_release_key (fentry->extension_sort_key, case_sensitive);
+ fentry->extension_sort_key = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If you change handle_dirent then check also handle_path.
+ * @return FALSE = don't add, TRUE = add to the list
+ */
+
+static gboolean
+handle_dirent (struct vfs_dirent *dp, const file_filter_t * filter, struct stat *buf1,
+ gboolean * link_to_dir, gboolean * stale_link)
+{
+ vfs_path_t *vpath;
+ gboolean ok = TRUE;
+
+ if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
+ return FALSE;
+ if (!panels_options.show_dot_files && (dp->d_name[0] == '.'))
+ return FALSE;
+ if (!panels_options.show_backups && dp->d_name[strlen (dp->d_name) - 1] == '~')
+ return FALSE;
+
+ vpath = vfs_path_from_str (dp->d_name);
+ if (mc_lstat (vpath, buf1) == -1)
+ {
+ /*
+ * lstat() fails - such entries should be identified by
+ * buf1->st_mode being 0.
+ * It happens on QNX Neutrino for /fs/cd0 if no CD is inserted.
+ */
+ memset (buf1, 0, sizeof (*buf1));
+ }
+
+ if (S_ISDIR (buf1->st_mode))
+ tree_store_mark_checked (dp->d_name);
+
+ /* A link to a file or a directory? */
+ *link_to_dir = file_is_symlink_to_dir (vpath, buf1, stale_link);
+
+ vfs_path_free (vpath, TRUE);
+
+ if (filter != NULL && filter->handler != NULL)
+ {
+ gboolean files_only = (filter->flags & SELECT_FILES_ONLY) != 0;
+
+ ok = ((S_ISDIR (buf1->st_mode) || *link_to_dir) && files_only)
+ || mc_search_run (filter->handler, dp->d_name, 0, strlen (dp->d_name), NULL);
+ }
+
+ return ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** get info about ".." */
+
+static gboolean
+dir_get_dotdot_stat (const vfs_path_t * vpath, struct stat *st)
+{
+ gboolean ret = FALSE;
+
+ if ((vpath != NULL) && (st != NULL))
+ {
+ const char *path;
+
+ path = vfs_path_get_by_index (vpath, 0)->path;
+ if (path != NULL && *path != '\0')
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vpath, "..", (char *) NULL);
+ ret = mc_stat (tmp_vpath, st) == 0;
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+alloc_dir_copy (int size)
+{
+ if (dir_copy.size < size)
+ {
+ if (dir_copy.list != NULL)
+ dir_list_free_list (&dir_copy);
+
+ dir_copy.list = g_new0 (file_entry_t, size);
+ dir_copy.size = size;
+ dir_copy.len = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Increase or decrease directory list size.
+ *
+ * @param list directory list
+ * @param delta value by increase (if positive) or decrease (if negative) list size
+ *
+ * @return FALSE on failure, TRUE on success
+ */
+
+gboolean
+dir_list_grow (dir_list * list, int delta)
+{
+ int size;
+ gboolean clear_flag = FALSE;
+
+ if (list == NULL)
+ return FALSE;
+
+ if (delta == 0)
+ return TRUE;
+
+ size = list->size + delta;
+ if (size <= 0)
+ {
+ size = DIR_LIST_MIN_SIZE;
+ clear_flag = TRUE;
+ }
+
+ if (size != list->size)
+ {
+ file_entry_t *fe;
+
+ fe = g_try_renew (file_entry_t, list->list, size);
+ if (fe == NULL)
+ return FALSE;
+
+ list->list = fe;
+ list->size = size;
+ }
+
+ list->len = clear_flag ? 0 : MIN (list->len, size);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Append file info to the directory list.
+ *
+ * @param list directory list
+ * @param fname file name
+ * @param st file stat info
+ * @param link_to_dir is file link to directory
+ * @param stale_link is file stale elink
+ *
+ * @return FALSE on failure, TRUE on success
+ */
+
+gboolean
+dir_list_append (dir_list * list, const char *fname, const struct stat * st,
+ gboolean link_to_dir, gboolean stale_link)
+{
+ file_entry_t *fentry;
+
+ /* Need to grow the *list? */
+ if (list->len == list->size && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
+ return FALSE;
+
+ fentry = &list->list[list->len];
+ fentry->fname = g_string_new (fname);
+ fentry->f.marked = 0;
+ fentry->f.link_to_dir = link_to_dir ? 1 : 0;
+ fentry->f.stale_link = stale_link ? 1 : 0;
+ fentry->f.dir_size_computed = 0;
+ fentry->st = *st;
+ fentry->name_sort_key = NULL;
+ fentry->extension_sort_key = NULL;
+
+ list->len++;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+unsorted (file_entry_t * a, file_entry_t * b)
+{
+ (void) a;
+ (void) b;
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_name (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ return compare_by_names (a, b);
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_vers (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result;
+
+ result = filevercmp (a->fname->str, b->fname->str);
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_ext (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int r;
+
+ if (a->extension_sort_key == NULL)
+ a->extension_sort_key = str_create_key (extension (a->fname->str), case_sensitive);
+ if (b->extension_sort_key == NULL)
+ b->extension_sort_key = str_create_key (extension (b->fname->str), case_sensitive);
+
+ r = str_key_collate (a->extension_sort_key, b->extension_sort_key, case_sensitive);
+ if (r != 0)
+ return r * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_time (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_mtime, b->st.st_mtime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_ctime (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_ctime, b->st.st_ctime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_atime (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_atime, b->st.st_atime);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_inode (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ return (a->st.st_ino - b->st.st_ino) * reverse;
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+sort_size (file_entry_t * a, file_entry_t * b)
+{
+ int ad = MY_ISDIR (a);
+ int bd = MY_ISDIR (b);
+
+ if (ad == bd || panels_options.mix_all_files)
+ {
+ int result = _GL_CMP (a->st.st_size, b->st.st_size);
+
+ if (result != 0)
+ return result * reverse;
+
+ return compare_by_names (a, b);
+ }
+
+ return bd - ad;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op)
+{
+ if (list->len > 1 && sort != (GCompareFunc) unsorted)
+ {
+ file_entry_t *fentry = &list->list[0];
+ int dot_dot_found;
+
+ /* If there is an ".." entry the caller must take care to
+ ensure that it occupies the first list element. */
+ dot_dot_found = DIR_IS_DOTDOT (fentry->fname->str) ? 1 : 0;
+ reverse = sort_op->reverse ? -1 : 1;
+ case_sensitive = sort_op->case_sensitive ? 1 : 0;
+ exec_first = sort_op->exec_first;
+ qsort (&(list->list)[dot_dot_found], list->len - dot_dot_found, sizeof (file_entry_t),
+ sort);
+
+ clean_sort_keys (list, dot_dot_found, list->len - dot_dot_found);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_clean (dir_list * list)
+{
+ int i;
+
+ for (i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i];
+ g_string_free (fentry->fname, TRUE);
+ fentry->fname = NULL;
+ }
+
+ list->len = 0;
+ /* reduce memory usage */
+ dir_list_grow (list, DIR_LIST_MIN_SIZE - list->size);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dir_list_free_list (dir_list * list)
+{
+ int i;
+
+ for (i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[i];
+ g_string_free (fentry->fname, TRUE);
+ }
+
+ MC_PTR_FREE (list->list);
+ list->len = 0;
+ list->size = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Used to set up a directory list when there is no access to a directory */
+
+gboolean
+dir_list_init (dir_list * list)
+{
+ file_entry_t *fentry;
+
+ /* Need to grow the *list? */
+ if (list->size == 0 && !dir_list_grow (list, DIR_LIST_RESIZE_STEP))
+ {
+ list->len = 0;
+ return FALSE;
+ }
+
+ fentry = &list->list[0];
+ memset (fentry, 0, sizeof (*fentry));
+ fentry->fname = g_string_new ("..");
+ fentry->f.link_to_dir = 0;
+ fentry->f.stale_link = 0;
+ fentry->f.dir_size_computed = 0;
+ fentry->f.marked = 0;
+ fentry->st.st_mode = 040755;
+ list->len = 1;
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ handle_path is a simplified handle_dirent. The difference is that
+ handle_path doesn't pay attention to panels_options.show_dot_files
+ and panels_options.show_backups.
+ Moreover handle_path can't be used with a filemask.
+ If you change handle_path then check also handle_dirent. */
+/* Return values: FALSE = don't add, TRUE = add to the list */
+
+gboolean
+handle_path (const char *path, struct stat * buf1, gboolean * link_to_dir, gboolean * stale_link)
+{
+ vfs_path_t *vpath;
+
+ if (DIR_IS_DOT (path) || DIR_IS_DOTDOT (path))
+ return FALSE;
+
+ vpath = vfs_path_from_str (path);
+ if (mc_lstat (vpath, buf1) == -1)
+ {
+ vfs_path_free (vpath, TRUE);
+ return FALSE;
+ }
+
+ if (S_ISDIR (buf1->st_mode))
+ tree_store_mark_checked (path);
+
+ /* A link to a file or a directory? */
+ *link_to_dir = FALSE;
+ *stale_link = FALSE;
+ if (S_ISLNK (buf1->st_mode))
+ {
+ struct stat buf2;
+
+ if (mc_stat (vpath, &buf2) == 0)
+ *link_to_dir = S_ISDIR (buf2.st_mode) != 0;
+ else
+ *stale_link = TRUE;
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter)
+{
+ DIR *dirp;
+ struct vfs_dirent *dp;
+ struct stat st;
+ file_entry_t *fentry;
+ const char *vpath_str;
+ gboolean ret = TRUE;
+
+ /* ".." (if any) must be the first entry in the list */
+ if (!dir_list_init (list))
+ return FALSE;
+
+ fentry = &list->list[0];
+ if (dir_get_dotdot_stat (vpath, &st))
+ fentry->st = st;
+
+ if (list->callback != NULL)
+ list->callback (DIR_OPEN, (void *) vpath);
+ dirp = mc_opendir (vpath);
+ if (dirp == NULL)
+ return FALSE;
+
+ tree_store_start_check (vpath);
+
+ vpath_str = vfs_path_as_str (vpath);
+ /* Do not add a ".." entry to the root directory */
+ if (IS_PATH_SEP (vpath_str[0]) && vpath_str[1] == '\0')
+ dir_list_clean (list);
+
+ while (ret && (dp = mc_readdir (dirp)) != NULL)
+ {
+ gboolean link_to_dir, stale_link;
+
+ if (list->callback != NULL)
+ list->callback (DIR_READ, dp);
+
+ if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
+ continue;
+
+ if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
+ ret = FALSE;
+ }
+
+ if (ret)
+ dir_list_sort (list, sort, sort_op);
+
+ if (list->callback != NULL)
+ list->callback (DIR_CLOSE, NULL);
+ mc_closedir (dirp);
+ tree_store_end_check ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+if_link_is_exe (const vfs_path_t * full_name_vpath, const file_entry_t * file)
+{
+ struct stat b;
+
+ if (S_ISLNK (file->st.st_mode) && mc_stat (full_name_vpath, &b) == 0)
+ return is_exe (b.st_mode);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** If filter is null, then it is a match */
+
+gboolean
+dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter)
+{
+ DIR *dirp;
+ struct vfs_dirent *dp;
+ int i;
+ struct stat st;
+ int marked_cnt;
+ GHashTable *marked_files;
+ const char *tmp_path;
+ gboolean ret = TRUE;
+
+ if (list->callback != NULL)
+ list->callback (DIR_OPEN, (void *) vpath);
+ dirp = mc_opendir (vpath);
+ if (dirp == NULL)
+ {
+ dir_list_clean (list);
+ dir_list_init (list);
+ return FALSE;
+ }
+
+ tree_store_start_check (vpath);
+
+ marked_files = g_hash_table_new (g_str_hash, g_str_equal);
+ alloc_dir_copy (list->len);
+ for (marked_cnt = i = 0; i < list->len; i++)
+ {
+ file_entry_t *fentry, *dfentry;
+
+ fentry = &list->list[i];
+ dfentry = &dir_copy.list[i];
+
+ dfentry->fname = mc_g_string_dup (fentry->fname);
+ dfentry->f.marked = fentry->f.marked;
+ dfentry->f.dir_size_computed = fentry->f.dir_size_computed;
+ dfentry->f.link_to_dir = fentry->f.link_to_dir;
+ dfentry->f.stale_link = fentry->f.stale_link;
+ dfentry->name_sort_key = NULL;
+ dfentry->extension_sort_key = NULL;
+ if (fentry->f.marked != 0)
+ {
+ g_hash_table_insert (marked_files, dfentry->fname->str, dfentry);
+ marked_cnt++;
+ }
+ }
+
+ /* save len for later dir_list_clean() */
+ dir_copy.len = list->len;
+
+ /* Add ".." except to the root directory. The ".." entry
+ (if any) must be the first in the list. */
+ tmp_path = vfs_path_get_by_index (vpath, 0)->path;
+ if (vfs_path_elements_count (vpath) == 1 && IS_PATH_SEP (tmp_path[0]) && tmp_path[1] == '\0')
+ {
+ /* root directory */
+ dir_list_clean (list);
+ }
+ else
+ {
+ dir_list_clean (list);
+ if (!dir_list_init (list))
+ {
+ dir_list_free_list (&dir_copy);
+ mc_closedir (dirp);
+ return FALSE;
+ }
+
+ if (dir_get_dotdot_stat (vpath, &st))
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[0];
+ fentry->st = st;
+ }
+ }
+
+ while (ret && (dp = mc_readdir (dirp)) != NULL)
+ {
+ gboolean link_to_dir, stale_link;
+
+ if (list->callback != NULL)
+ list->callback (DIR_READ, dp);
+
+ if (!handle_dirent (dp, filter, &st, &link_to_dir, &stale_link))
+ continue;
+
+ if (!dir_list_append (list, dp->d_name, &st, link_to_dir, stale_link))
+ ret = FALSE;
+ else
+ {
+ file_entry_t *fentry;
+
+ fentry = &list->list[list->len - 1];
+
+ /*
+ * If we have marked files in the copy, scan through the copy
+ * to find matching file. Decrease number of remaining marks if
+ * we copied one.
+ */
+ fentry->f.marked = (marked_cnt > 0
+ && g_hash_table_lookup (marked_files, dp->d_name) != NULL) ? 1 : 0;
+ if (fentry->f.marked != 0)
+ marked_cnt--;
+ }
+ }
+
+ if (ret)
+ dir_list_sort (list, sort, sort_op);
+
+ if (list->callback != NULL)
+ list->callback (DIR_CLOSE, NULL);
+ mc_closedir (dirp);
+ tree_store_end_check ();
+
+ g_hash_table_destroy (marked_files);
+ dir_list_free_list (&dir_copy);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_filter_clear (file_filter_t * filter)
+{
+ MC_PTR_FREE (filter->value);
+ mc_search_free (filter->handler);
+ filter->handler = NULL;
+ /* keep filter->flags */
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/dir.h b/src/filemanager/dir.h
new file mode 100644
index 0000000..80a19df
--- /dev/null
+++ b/src/filemanager/dir.h
@@ -0,0 +1,115 @@
+/** \file dir.h
+ * \brief Header: directory routines
+ */
+
+#ifndef MC__DIR_H
+#define MC__DIR_H
+
+#include <sys/stat.h>
+
+#include "lib/global.h"
+#include "lib/search.h"
+#include "lib/file-entry.h"
+#include "lib/vfs/vfs.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DIR_LIST_MIN_SIZE 128
+#define DIR_LIST_RESIZE_STEP 128
+
+typedef enum
+{
+ DIR_OPEN = 0,
+ DIR_READ,
+ DIR_CLOSE
+} dir_list_cb_state_t;
+
+/* selection flags */
+typedef enum
+{
+ SELECT_FILES_ONLY = 1 << 0,
+ SELECT_MATCH_CASE = 1 << 1,
+ SELECT_SHELL_PATTERNS = 1 << 2
+} select_flags_t;
+
+#define FILE_FILTER_DEFAULT_FLAGS (SELECT_FILES_ONLY | SELECT_MATCH_CASE | SELECT_SHELL_PATTERNS)
+
+/* dir_list callback */
+typedef void (*dir_list_cb_fn) (dir_list_cb_state_t state, void *data);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/**
+ * A structure to represent directory content
+ */
+typedef struct
+{
+ file_entry_t *list; /**< list of file_entry_t objects */
+ int size; /**< number of allocated elements in list (capacity) */
+ int len; /**< number of used elements in list */
+ dir_list_cb_fn callback; /**< callback to visualize of directory read */
+} dir_list;
+
+/**
+ * A structure to represent sort options for directory content
+ */
+typedef struct dir_sort_options_struct
+{
+ gboolean reverse; /**< sort is reverse */
+ gboolean case_sensitive; /**< sort is case sensitive */
+ gboolean exec_first; /**< executables are at top of list */
+} dir_sort_options_t;
+
+/* filter */
+typedef struct
+{
+ char *value;
+ mc_search_t *handler;
+ select_flags_t flags;
+} file_filter_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean dir_list_grow (dir_list * list, int delta);
+gboolean dir_list_append (dir_list * list, const char *fname, const struct stat *st,
+ gboolean link_to_dir, gboolean stale_link);
+
+gboolean dir_list_load (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter);
+gboolean dir_list_reload (dir_list * list, const vfs_path_t * vpath, GCompareFunc sort,
+ const dir_sort_options_t * sort_op, const file_filter_t * filter);
+void dir_list_sort (dir_list * list, GCompareFunc sort, const dir_sort_options_t * sort_op);
+gboolean dir_list_init (dir_list * list);
+void dir_list_clean (dir_list * list);
+void dir_list_free_list (dir_list * list);
+gboolean handle_path (const char *path, struct stat *buf1, gboolean * link_to_dir,
+ gboolean * stale_link);
+
+/* Sorting functions */
+int unsorted (file_entry_t * a, file_entry_t * b);
+int sort_name (file_entry_t * a, file_entry_t * b);
+int sort_vers (file_entry_t * a, file_entry_t * b);
+int sort_ext (file_entry_t * a, file_entry_t * b);
+int sort_time (file_entry_t * a, file_entry_t * b);
+int sort_atime (file_entry_t * a, file_entry_t * b);
+int sort_ctime (file_entry_t * a, file_entry_t * b);
+int sort_size (file_entry_t * a, file_entry_t * b);
+int sort_inode (file_entry_t * a, file_entry_t * b);
+
+gboolean if_link_is_exe (const vfs_path_t * full_name, const file_entry_t * file);
+
+void file_filter_clear (file_filter_t * filter);
+
+/*** inline functions ****************************************************************************/
+
+static inline gboolean
+link_isdir (const file_entry_t * file)
+{
+ return (file->f.link_to_dir != 0);
+}
+
+#endif /* MC__DIR_H */
diff --git a/src/filemanager/ext.c b/src/filemanager/ext.c
new file mode 100644
index 0000000..b21c4d0
--- /dev/null
+++ b/src/filemanager/ext.c
@@ -0,0 +1,1089 @@
+/*
+ Extension dependent execution.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Miguel de Icaza, 1994
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file ext.c
+ * \brief Source: extension dependent execution
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/search.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_index */
+#endif
+
+#ifdef USE_FILE_CMD
+#include "src/setup.h" /* use_file_to_check_type */
+#endif
+#include "src/execute.h"
+#include "src/history.h"
+#include "src/usermenu.h"
+
+#include "src/consaver/cons.saver.h"
+#include "src/viewer/mcviewer.h"
+
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h" /* do_set_codepage */
+#endif
+
+#include "filemanager.h" /* current_panel */
+#include "panel.h" /* panel_cd */
+
+#include "ext.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef USE_FILE_CMD
+#ifdef FILE_B
+#define FILE_CMD "file -z " FILE_B FILE_S FILE_L
+#else
+#define FILE_CMD "file -z " FILE_S FILE_L
+#endif
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef char *(*quote_func_t) (const char *name, gboolean quote_percent);
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* This variable points to a copy of the mc.ext file in memory
+ * With this we avoid loading/parsing the file each time we
+ * need it
+ */
+static mc_config_t *ext_ini = NULL;
+static gchar **ext_ini_groups = NULL;
+static vfs_path_t *localfilecopy_vpath = NULL;
+static char buffer[BUF_1K];
+
+static char *pbuffer = NULL;
+static time_t localmtime = 0;
+static quote_func_t quote_func = name_quote;
+static gboolean run_view = FALSE;
+static gboolean is_cd = FALSE;
+static gboolean written_nonspace = FALSE;
+static gboolean do_local_copy = FALSE;
+
+static const char *descr_group = "mc.ext.ini";
+static const char *default_group = "Default";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_cleanup_script (vfs_path_t * script_vpath)
+{
+ if (script_vpath != NULL)
+ {
+ (void) mc_unlink (script_vpath);
+ vfs_path_free (script_vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_cleanup_file_name (const vfs_path_t * filename_vpath, gboolean has_changed)
+{
+ if (localfilecopy_vpath == NULL)
+ return;
+
+ if (has_changed)
+ {
+ struct stat mystat;
+
+ mc_stat (localfilecopy_vpath, &mystat);
+ has_changed = localmtime != mystat.st_mtime;
+ }
+ mc_ungetlocalcopy (filename_vpath, localfilecopy_vpath, has_changed);
+ vfs_path_free (localfilecopy_vpath, TRUE);
+ localfilecopy_vpath = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+exec_get_file_name (const vfs_path_t * filename_vpath)
+{
+ if (!do_local_copy)
+ return quote_func (vfs_path_get_last_path_str (filename_vpath), FALSE);
+
+ if (localfilecopy_vpath == NULL)
+ {
+ struct stat mystat;
+ localfilecopy_vpath = mc_getlocalcopy (filename_vpath);
+ if (localfilecopy_vpath == NULL)
+ return NULL;
+
+ mc_stat (localfilecopy_vpath, &mystat);
+ localmtime = mystat.st_mtime;
+ }
+
+ return quote_func (vfs_path_get_last_path_str (localfilecopy_vpath), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+exec_expand_format (char symbol, gboolean is_result_quoted)
+{
+ char *text;
+
+ text = expand_format (NULL, symbol, TRUE);
+ if (is_result_quoted && text != NULL)
+ {
+ char *quoted_text;
+
+ quoted_text = g_strdup_printf ("\"%s\"", text);
+ g_free (text);
+ text = quoted_text;
+ }
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+exec_get_export_variables (const vfs_path_t * filename_vpath)
+{
+ char *text;
+ GString *export_vars_string;
+ size_t i;
+
+ /* *INDENT-OFF* */
+ struct
+ {
+ const char symbol;
+ const char *name;
+ const gboolean is_result_quoted;
+ } export_variables[] = {
+ {'p', "MC_EXT_BASENAME", FALSE},
+ {'d', "MC_EXT_CURRENTDIR", FALSE},
+ {'s', "MC_EXT_SELECTED", TRUE},
+ {'t', "MC_EXT_ONLYTAGGED", TRUE},
+ {'\0', NULL, FALSE}
+ };
+ /* *INDENT-ON* */
+
+ text = exec_get_file_name (filename_vpath);
+ if (text == NULL)
+ return NULL;
+
+ export_vars_string = g_string_new ("MC_EXT_FILENAME=");
+ g_string_append_printf (export_vars_string, "%s\nexport MC_EXT_FILENAME\n", text);
+ g_free (text);
+
+ for (i = 0; export_variables[i].name != NULL; i++)
+ {
+ text =
+ exec_expand_format (export_variables[i].symbol, export_variables[i].is_result_quoted);
+ if (text != NULL)
+ {
+ g_string_append_printf (export_vars_string,
+ "%s=%s\nexport %s\n", export_variables[i].name, text,
+ export_variables[i].name);
+ g_free (text);
+ }
+ }
+
+ return export_vars_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GString *
+exec_make_shell_string (const char *lc_data, const vfs_path_t * filename_vpath)
+{
+ GString *shell_string;
+ char lc_prompt[80] = "\0";
+ gboolean parameter_found = FALSE;
+ gboolean expand_prefix_found = FALSE;
+
+ shell_string = g_string_new ("");
+
+ for (; *lc_data != '\0' && *lc_data != '\n'; lc_data++)
+ {
+ if (parameter_found)
+ {
+ if (*lc_data == '}')
+ {
+ char *parameter;
+
+ parameter_found = FALSE;
+ parameter =
+ input_dialog (_("Parameter"), lc_prompt, MC_HISTORY_EXT_PARAMETER, "",
+ INPUT_COMPLETE_NONE);
+ if (parameter == NULL)
+ {
+ /* User canceled */
+ g_string_free (shell_string, TRUE);
+ exec_cleanup_file_name (filename_vpath, FALSE);
+ return NULL;
+ }
+ g_string_append (shell_string, parameter);
+ written_nonspace = TRUE;
+ g_free (parameter);
+ }
+ else
+ {
+ size_t len = strlen (lc_prompt);
+
+ if (len < sizeof (lc_prompt) - 1)
+ {
+ lc_prompt[len] = *lc_data;
+ lc_prompt[len + 1] = '\0';
+ }
+ }
+ }
+ else if (expand_prefix_found)
+ {
+ expand_prefix_found = FALSE;
+ if (*lc_data == '{')
+ parameter_found = TRUE;
+ else
+ {
+ int i;
+
+ i = check_format_view (lc_data);
+ if (i != 0)
+ {
+ lc_data += i - 1;
+ run_view = TRUE;
+ }
+ else
+ {
+ i = check_format_cd (lc_data);
+ if (i > 0)
+ {
+ is_cd = TRUE;
+ quote_func = fake_name_quote;
+ do_local_copy = FALSE;
+ pbuffer = buffer;
+ lc_data += i - 1;
+ }
+ else
+ {
+ char *v;
+
+ i = check_format_var (lc_data, &v);
+ if (i > 0)
+ {
+ g_string_append (shell_string, v);
+ g_free (v);
+ lc_data += i;
+ }
+ else
+ {
+ char *text;
+
+ if (*lc_data != 'f')
+ text = expand_format (NULL, *lc_data, !is_cd);
+ else
+ {
+ text = exec_get_file_name (filename_vpath);
+ if (text == NULL)
+ {
+ g_string_free (shell_string, TRUE);
+ return NULL;
+ }
+ }
+
+ if (!is_cd)
+ g_string_append (shell_string, text);
+ else
+ {
+ strcpy (pbuffer, text);
+ pbuffer = strchr (pbuffer, 0);
+ }
+
+ g_free (text);
+ written_nonspace = TRUE;
+ }
+ }
+ }
+ }
+ }
+ else if (*lc_data == '%')
+ expand_prefix_found = TRUE;
+ else
+ {
+ if (!whitespace (*lc_data))
+ written_nonspace = TRUE;
+ if (is_cd)
+ *(pbuffer++) = *lc_data;
+ else
+ g_string_append_c (shell_string, *lc_data);
+ }
+ } /* for */
+
+ return shell_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_extension_view (void *target, char *cmd, const vfs_path_t * filename_vpath, int start_line)
+{
+ mcview_mode_flags_t def_flags = {
+ /* *INDENT-OFF* */
+ .wrap = FALSE,
+ .hex = mcview_global_flags.hex,
+ .magic = FALSE,
+ .nroff = mcview_global_flags.nroff
+ /* *INDENT-ON* */
+ };
+
+ mcview_mode_flags_t changed_flags;
+
+ mcview_clear_mode_flags (&changed_flags);
+ mcview_altered_flags.hex = FALSE;
+ mcview_altered_flags.nroff = FALSE;
+ if (def_flags.hex != mcview_global_flags.hex)
+ changed_flags.hex = TRUE;
+ if (def_flags.nroff != mcview_global_flags.nroff)
+ changed_flags.nroff = TRUE;
+
+ if (target == NULL)
+ mcview_viewer (cmd, filename_vpath, start_line, 0, 0);
+ else
+ mcview_load ((WView *) target, cmd, vfs_path_as_str (filename_vpath), start_line, 0, 0);
+
+ if (changed_flags.hex && !mcview_altered_flags.hex)
+ mcview_global_flags.hex = def_flags.hex;
+ if (changed_flags.nroff && !mcview_altered_flags.nroff)
+ mcview_global_flags.nroff = def_flags.nroff;
+
+ dialog_switch_process_pending ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+exec_extension_cd (WPanel * panel)
+{
+ char *q;
+ vfs_path_t *p_vpath;
+
+ *pbuffer = '\0';
+ pbuffer = buffer;
+ /* Search last non-space character. Start search at the end in order
+ not to short filenames containing spaces. */
+ q = pbuffer + strlen (pbuffer) - 1;
+ while (q >= pbuffer && whitespace (*q))
+ q--;
+ q[1] = 0;
+
+ p_vpath = vfs_path_from_str_flags (pbuffer, VPF_NO_CANON);
+ panel_cd (panel, p_vpath, cd_parse_command);
+ vfs_path_free (p_vpath, TRUE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+exec_extension (WPanel * panel, void *target, const vfs_path_t * filename_vpath,
+ const char *lc_data, int start_line)
+{
+ GString *shell_string, *export_variables;
+ vfs_path_t *script_vpath = NULL;
+ int cmd_file_fd;
+ FILE *cmd_file;
+ char *cmd = NULL;
+
+ pbuffer = NULL;
+ localmtime = 0;
+ quote_func = name_quote;
+ run_view = FALSE;
+ is_cd = FALSE;
+ written_nonspace = FALSE;
+
+ /* Avoid making a local copy if we are doing a cd */
+ do_local_copy = !vfs_file_is_local (filename_vpath);
+
+ shell_string = exec_make_shell_string (lc_data, filename_vpath);
+ if (shell_string == NULL)
+ goto ret;
+
+ if (is_cd)
+ {
+ exec_extension_cd (panel);
+ g_string_free (shell_string, TRUE);
+ goto ret;
+ }
+
+ /*
+ * All commands should be run in /bin/sh regardless of user shell.
+ * To do that, create temporary shell script and run it.
+ * Sometimes it's not needed (e.g. for %cd and %view commands),
+ * but it's easier to create it anyway.
+ */
+ cmd_file_fd = mc_mkstemps (&script_vpath, "mcext", SCRIPT_SUFFIX);
+
+ if (cmd_file_fd == -1)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot create temporary command file\n%s"), unix_error_string (errno));
+ g_string_free (shell_string, TRUE);
+ goto ret;
+ }
+
+ cmd_file = fdopen (cmd_file_fd, "w");
+ fputs ("#! /bin/sh\n\n", cmd_file);
+
+ export_variables = exec_get_export_variables (filename_vpath);
+ if (export_variables != NULL)
+ {
+ fputs (export_variables->str, cmd_file);
+ g_string_free (export_variables, TRUE);
+ }
+
+ fputs (shell_string->str, cmd_file);
+ g_string_free (shell_string, TRUE);
+
+ /*
+ * Make the script remove itself when it finishes.
+ * Don't do it for the viewer - it may need to rerun the script,
+ * so we clean up after calling view().
+ */
+ if (!run_view)
+ fprintf (cmd_file, "\n/bin/rm -f %s\n", vfs_path_as_str (script_vpath));
+
+ fclose (cmd_file);
+
+ if ((run_view && !written_nonspace) || is_cd)
+ {
+ exec_cleanup_script (script_vpath);
+ script_vpath = NULL;
+ }
+ else
+ {
+ /* Set executable flag on the command file ... */
+ mc_chmod (script_vpath, S_IRWXU);
+ /* ... but don't rely on it - run /bin/sh explicitly */
+ cmd = g_strconcat ("/bin/sh ", vfs_path_as_str (script_vpath), (char *) NULL);
+ }
+
+ if (run_view)
+ {
+ /* If we've written whitespace only, then just load filename into view */
+ if (!written_nonspace)
+ exec_extension_view (target, NULL, filename_vpath, start_line);
+ else
+ exec_extension_view (target, cmd, filename_vpath, start_line);
+ }
+ else
+ {
+ shell_execute (cmd, EXECUTE_INTERNAL);
+ if (mc_global.tty.console_flag != '\0')
+ {
+ handle_console (CONSOLE_SAVE);
+ if (output_lines != 0 && mc_global.keybar_visible)
+ {
+ unsigned char end_line;
+
+ end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+ }
+ }
+
+ g_free (cmd);
+
+ exec_cleanup_file_name (filename_vpath, TRUE);
+ ret:
+ return script_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run cmd_file with args, put result into buf.
+ * If error, put '\0' into buf[0]
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ *
+ * NOTES: buf is null-terminated string.
+ */
+
+#ifdef USE_FILE_CMD
+static int
+get_popen_information (const char *cmd_file, const char *args, char *buf, int buflen)
+{
+ gboolean read_bytes = FALSE;
+ char *command;
+ FILE *f;
+
+ command = g_strconcat (cmd_file, args, " 2>/dev/null", (char *) NULL);
+ f = popen (command, "r");
+ g_free (command);
+
+ if (f != NULL)
+ {
+#ifdef __QNXNTO__
+ if (setvbuf (f, NULL, _IOFBF, 0) != 0)
+ {
+ (void) pclose (f);
+ return -1;
+ }
+#endif
+ read_bytes = (fgets (buf, buflen, f) != NULL);
+ if (!read_bytes)
+ buf[0] = '\0'; /* Paranoid termination */
+ pclose (f);
+ }
+ else
+ {
+ buf[0] = '\0'; /* Paranoid termination */
+ return -1;
+ }
+
+ buf[buflen - 1] = '\0';
+
+ return read_bytes ? 1 : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run the "file" command on the local file.
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ */
+
+static int
+get_file_type_local (const vfs_path_t * filename_vpath, char *buf, int buflen)
+{
+ char *tmp;
+ int ret;
+
+ tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
+ ret = get_popen_information (FILE_CMD, tmp, buf, buflen);
+ g_free (tmp);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Run the "enca" command on the local file.
+ * Return 1 if the data is valid, 0 otherwise, -1 for fatal errors.
+ */
+
+#ifdef HAVE_CHARSET
+static int
+get_file_encoding_local (const vfs_path_t * filename_vpath, char *buf, int buflen)
+{
+ char *tmp, *lang, *args;
+ int ret;
+
+ tmp = name_quote (vfs_path_get_last_path_str (filename_vpath), FALSE);
+ lang = name_quote (autodetect_codeset, FALSE);
+ args = g_strconcat (" -L", lang, " -i ", tmp, (char *) NULL);
+
+ ret = get_popen_information ("enca", args, buf, buflen);
+
+ g_free (args);
+ g_free (lang);
+ g_free (tmp);
+
+ return ret;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Invoke the "file" command on the file and match its output against PTR.
+ * have_type is a flag that is set if we already have tried to determine
+ * the type of that file.
+ * Return TRUE for match, FALSE otherwise.
+ */
+
+static gboolean
+regex_check_type (const vfs_path_t * filename_vpath, const char *ptr, gboolean case_insense,
+ gboolean * have_type, GError ** mcerror)
+{
+ gboolean found = FALSE;
+
+ /* Following variables are valid if *have_type is TRUE */
+ static char content_string[2048];
+ static size_t content_shift = 0;
+ static int got_data = 0;
+
+ mc_return_val_if_error (mcerror, FALSE);
+
+ if (!*have_type)
+ {
+ vfs_path_t *localfile_vpath;
+
+#ifdef HAVE_CHARSET
+ static char encoding_id[21]; /* CSISO51INISCYRILLIC -- 20 */
+ int got_encoding_data;
+#endif /* HAVE_CHARSET */
+
+ /* Don't repeate even unsuccessful checks */
+ *have_type = TRUE;
+
+ localfile_vpath = mc_getlocalcopy (filename_vpath);
+ if (localfile_vpath == NULL)
+ {
+ mc_propagate_error (mcerror, 0, _("Cannot fetch a local copy of %s"),
+ vfs_path_as_str (filename_vpath));
+ return FALSE;
+ }
+
+
+#ifdef HAVE_CHARSET
+ got_encoding_data = is_autodetect_codeset_enabled
+ ? get_file_encoding_local (localfile_vpath, encoding_id, sizeof (encoding_id)) : 0;
+
+ if (got_encoding_data > 0)
+ {
+ char *pp;
+ int cp_id;
+
+ pp = strchr (encoding_id, '\n');
+ if (pp != NULL)
+ *pp = '\0';
+
+ cp_id = get_codepage_index (encoding_id);
+ if (cp_id == -1)
+ cp_id = default_source_codepage;
+
+ do_set_codepage (cp_id);
+ }
+#endif /* HAVE_CHARSET */
+
+ got_data = get_file_type_local (localfile_vpath, content_string, sizeof (content_string));
+
+ mc_ungetlocalcopy (filename_vpath, localfile_vpath, FALSE);
+
+ if (got_data > 0)
+ {
+ char *pp;
+
+ pp = strchr (content_string, '\n');
+ if (pp != NULL)
+ *pp = '\0';
+
+#ifndef FILE_B
+ {
+ const char *real_name; /* name used with "file" */
+ size_t real_len;
+
+ real_name = vfs_path_get_last_path_str (localfile_vpath);
+ real_len = strlen (real_name);
+
+ if (strncmp (content_string, real_name, real_len) == 0)
+ {
+ /* Skip "real_name: " */
+ content_shift = real_len;
+
+ /* Solaris' file prints tab(s) after ':' */
+ if (content_string[content_shift] == ':')
+ for (content_shift++; whitespace (content_string[content_shift]);
+ content_shift++)
+ ;
+ }
+ }
+#endif /* FILE_B */
+ }
+ else
+ {
+ /* No data */
+ content_string[0] = '\0';
+ }
+ vfs_path_free (localfile_vpath, TRUE);
+ }
+
+ if (got_data == -1)
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Pipe failed"));
+ return FALSE;
+ }
+
+ if (content_string[0] != '\0')
+ {
+ mc_search_t *search;
+
+ search = mc_search_new (ptr, DEFAULT_CHARSET);
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = !case_insense;
+ found = mc_search_run (search, content_string + content_shift, 0, -1, NULL);
+ mc_search_free (search);
+ }
+ else
+ {
+ mc_propagate_error (mcerror, 0, "%s", _("Regular expression error"));
+ }
+ }
+
+ return found;
+}
+#endif /* USE_FILE_CMD */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_old_extension_file (void)
+{
+ char *extension_old_file;
+
+ extension_old_file = mc_config_get_full_path (MC_EXT_OLD_FILE);
+ if (exist_file (extension_old_file))
+ message (D_ERROR, _("Warning"),
+ _("You have an outdated %s file.\nMidnight Commander now uses %s file.\n"
+ "Please copy your modifications of the old file to the new one."),
+ extension_old_file, MC_EXT_FILE);
+ g_free (extension_old_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+load_extension_file (void)
+{
+ char *extension_file;
+ gboolean mc_user_ext = TRUE;
+ gboolean home_error = FALSE;
+
+ extension_file = mc_config_get_full_path (MC_EXT_FILE);
+ if (!exist_file (extension_file))
+ {
+ g_free (extension_file);
+
+ check_old_extension_file ();
+
+ check_stock_mc_ext:
+ extension_file = mc_build_filename (mc_global.sysconfig_dir, MC_EXT_FILE, (char *) NULL);
+ if (!exist_file (extension_file))
+ {
+ g_free (extension_file);
+ extension_file =
+ mc_build_filename (mc_global.share_data_dir, MC_EXT_FILE, (char *) NULL);
+ if (!exist_file (extension_file))
+ MC_PTR_FREE (extension_file);
+ }
+ mc_user_ext = FALSE;
+ }
+
+ if (extension_file != NULL)
+ {
+ ext_ini = mc_config_init (extension_file, TRUE);
+ g_free (extension_file);
+ }
+ if (ext_ini == NULL)
+ return FALSE;
+
+ /* Check version */
+ if (!mc_config_has_group (ext_ini, descr_group))
+ {
+ flush_extension_file ();
+
+ if (!mc_user_ext)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("The format of the\n%s%s\nfile has changed with version 4.0.\n"
+ "It seems that the installation has failed.\nPlease fetch a fresh copy "
+ "from the Midnight Commander package."),
+ mc_global.sysconfig_dir, MC_EXT_FILE);
+ return FALSE;
+ }
+
+ home_error = TRUE;
+ goto check_stock_mc_ext;
+ }
+
+ if (home_error)
+ {
+ extension_file = mc_config_get_full_path (MC_EXT_FILE);
+ message (D_ERROR, MSG_ERROR,
+ _("The format of the\n%s\nfile has changed with version 4.0.\nYou may either want "
+ "to copy it from\n%s%s\nor use that file as an example of how to write it."),
+ extension_file, mc_global.sysconfig_dir, MC_EXT_FILE);
+ g_free (extension_file);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+flush_extension_file (void)
+{
+ g_strfreev (ext_ini_groups);
+ ext_ini_groups = NULL;
+
+ mc_config_deinit (ext_ini);
+ ext_ini = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * The second argument is action, i.e. Open, View or Edit
+ * Use target object to open file in.
+ *
+ * This function returns:
+ *
+ * -1 for a failure or user interrupt
+ * 0 if no command was run
+ * 1 if some command was run
+ *
+ * If action == "View" then a parameter is checked in the form of "View:%d",
+ * if the value for %d exists, then the viewer is started up at that line number.
+ */
+
+int
+regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action,
+ vfs_path_t ** script_vpath)
+{
+ const char *filename;
+ size_t filename_len;
+ gboolean found = FALSE;
+ gboolean error_flag = FALSE;
+ int ret = 0;
+ struct stat mystat;
+ int view_at_line_number = 0;
+#ifdef USE_FILE_CMD
+ gboolean have_type = FALSE; /* Flag used by regex_check_type() */
+#endif
+ char **group_iter;
+ char *include_group = NULL;
+ const char *current_group;
+
+ if (filename_vpath == NULL)
+ return 0;
+
+ if (script_vpath != NULL)
+ *script_vpath = NULL;
+
+ /* Check for the special View:%d parameter */
+ if (strncmp (action, "View:", 5) == 0)
+ {
+ view_at_line_number = atoi (action + 5);
+ action = "View";
+ }
+
+ if (ext_ini == NULL && !load_extension_file ())
+ return 0;
+
+ mc_stat (filename_vpath, &mystat);
+
+ filename = vfs_path_get_last_path_str (filename_vpath);
+ filename = x_basename (filename);
+ filename_len = strlen (filename);
+
+ if (ext_ini_groups == NULL)
+ ext_ini_groups = mc_config_get_groups (ext_ini, NULL);
+
+ /* find matched type, regex or shell pattern */
+ for (group_iter = ext_ini_groups; *group_iter != NULL && !found; group_iter++)
+ {
+ enum
+ {
+ TYPE_UNUSED,
+ TYPE_NOT_FOUND,
+ TYPE_FOUND
+ } type_state = TYPE_UNUSED;
+
+ const gchar *g = *group_iter;
+ gchar *pattern;
+ gboolean ignore_case;
+
+ if (strcmp (g, descr_group) == 0 || strncmp (g, "Include/", 8) == 0
+ || strcmp (g, default_group) == 0)
+ continue;
+
+ /* The "Directory" parameter is a special case: if it's present then
+ "Type", "Regex", and "Shell" parameters are ignored */
+ pattern = mc_config_get_string_raw (ext_ini, g, "Directory", NULL);
+ if (pattern != NULL)
+ {
+ found = S_ISDIR (mystat.st_mode)
+ && mc_search (pattern, DEFAULT_CHARSET, vfs_path_as_str (filename_vpath),
+ MC_SEARCH_T_REGEX);
+ g_free (pattern);
+
+ continue; /* stop if found */
+ }
+
+#ifdef USE_FILE_CMD
+ if (use_file_to_check_type)
+ {
+ pattern = mc_config_get_string_raw (ext_ini, g, "Type", NULL);
+ if (pattern != NULL)
+ {
+ GError *mcerror = NULL;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "TypeIgnoreCase", FALSE);
+ type_state =
+ regex_check_type (filename_vpath, pattern, ignore_case, &have_type, &mcerror)
+ ? TYPE_FOUND : TYPE_NOT_FOUND;
+ g_free (pattern);
+
+ if (mc_error_message (&mcerror, NULL))
+ error_flag = TRUE; /* leave it if file cannot be opened */
+
+ if (type_state == TYPE_NOT_FOUND)
+ continue;
+ }
+ }
+#endif /* USE_FILE_CMD */
+
+ pattern = mc_config_get_string_raw (ext_ini, g, "Regex", NULL);
+ if (pattern != NULL)
+ {
+ mc_search_t *search;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "RegexIgnoreCase", FALSE);
+ search = mc_search_new (pattern, DEFAULT_CHARSET);
+ g_free (pattern);
+
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = !ignore_case;
+ found = mc_search_run (search, filename, 0, filename_len, NULL);
+ mc_search_free (search);
+ }
+
+ found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
+ }
+ else
+ {
+ pattern = mc_config_get_string_raw (ext_ini, g, "Shell", NULL);
+ if (pattern != NULL)
+ {
+ int (*cmp_func) (const char *s1, const char *s2, size_t n);
+ size_t pattern_len;
+
+ ignore_case = mc_config_get_bool (ext_ini, g, "ShellIgnoreCase", FALSE);
+ cmp_func = ignore_case ? strncasecmp : strncmp;
+ pattern_len = strlen (pattern);
+
+ if (*pattern == '.' && filename_len >= pattern_len)
+ found =
+ cmp_func (pattern, filename + filename_len - pattern_len, pattern_len) == 0;
+ else
+ found = pattern_len == filename_len
+ && cmp_func (pattern, filename, filename_len) == 0;
+
+ g_free (pattern);
+
+ found = found && (type_state == TYPE_UNUSED || type_state == TYPE_FOUND);
+ }
+ else
+ found = type_state == TYPE_FOUND;
+ }
+ }
+
+ /* group is found, process actions */
+ if (found)
+ {
+ char *include_value;
+
+ group_iter--;
+
+ /* "Include" parameter has the highest priority over any actions */
+ include_value = mc_config_get_string_raw (ext_ini, *group_iter, "Include", NULL);
+ if (include_value != NULL)
+ {
+ /* find "Include/include_value" group */
+ include_group = g_strconcat ("Include/", include_value, (char *) NULL);
+ g_free (include_value);
+ found = mc_config_has_group (ext_ini, include_group);
+ }
+ }
+
+ if (found)
+ current_group = include_group != NULL ? include_group : *group_iter;
+ else
+ {
+ current_group = default_group;
+ found = mc_config_has_group (ext_ini, current_group);
+ }
+
+ if (found && !error_flag)
+ {
+ gchar *action_value;
+
+ action_value = mc_config_get_string_raw (ext_ini, current_group, action, NULL);
+ if (action_value == NULL)
+ {
+ /* Not found, try the action from default section */
+ action_value = mc_config_get_string_raw (ext_ini, default_group, action, NULL);
+ found = (action_value != NULL && *action_value != '\0');
+ }
+ else
+ {
+ /* If action's value is empty, ignore action from default section */
+ found = (*action_value != '\0');
+ }
+
+ if (found)
+ {
+ vfs_path_t *sv;
+
+ sv = exec_extension (current_panel, target, filename_vpath, action_value,
+ view_at_line_number);
+ if (script_vpath != NULL)
+ *script_vpath = sv;
+ else
+ exec_cleanup_script (sv);
+
+ ret = 1;
+ }
+
+ g_free (action_value);
+ }
+
+ g_free (include_group);
+
+ return (error_flag ? -1 : ret);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/ext.h b/src/filemanager/ext.h
new file mode 100644
index 0000000..771ca18
--- /dev/null
+++ b/src/filemanager/ext.h
@@ -0,0 +1,33 @@
+/** \file ext.h
+ * \brief Header: extension dependent execution
+ */
+
+#ifndef MC__EXT_H
+#define MC__EXT_H
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int regex_command_for (void *target, const vfs_path_t * filename_vpath, const char *action,
+ vfs_path_t ** script_vpath);
+
+/* Call it after the user has edited the mc.ext file,
+ * to flush the cached mc.ext file
+ */
+void flush_extension_file (void);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+regex_command (const vfs_path_t * filename_vpath, const char *action)
+{
+ return regex_command_for (NULL, filename_vpath, action, NULL);
+}
+
+#endif /* MC__EXT_H */
diff --git a/src/filemanager/file.c b/src/filemanager/file.c
new file mode 100644
index 0000000..fa2ef44
--- /dev/null
+++ b/src/filemanager/file.c
@@ -0,0 +1,3562 @@
+/*
+ File management.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1995
+ Fred Leeflang, 1994, 1995
+ Miguel de Icaza, 1994, 1995, 1996
+ Jakub Jelinek, 1995, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+
+ The copy code was based in GNU's cp, and was written by:
+ Torbjorn Granlund, David MacKenzie, and Jim Meyering.
+
+ The move code was based in GNU's mv, and was written by:
+ Mike Parker and David MacKenzie.
+
+ Janne Kukonlehto added much error recovery to them for being used
+ in an interactive program.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Please note that all dialogs used here must be safe for background
+ * operations.
+ */
+
+/** \file src/filemanager/file.c
+ * \brief Source: file management
+ */
+
+/* {{{ Include files */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/search.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+#include "lib/widget.h"
+
+#include "src/setup.h"
+#ifdef ENABLE_BACKGROUND
+#include "src/background.h" /* do_background() */
+#endif
+
+/* Needed for other_panel and WTree */
+#include "dir.h"
+#include "filegui.h"
+#include "filenot.h"
+#include "tree.h"
+#include "filemanager.h" /* other_panel */
+#include "layout.h" /* rotate_dash() */
+#include "ioblksize.h" /* io_blksize() */
+
+#include "file.h"
+
+/* }}} */
+
+/*** global variables ****************************************************************************/
+
+/* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
+const char *op_names[3] = {
+ N_("DialogTitle|Copy"),
+ N_("DialogTitle|Move"),
+ N_("DialogTitle|Delete")
+};
+
+/*** file scope macro definitions ****************************************************************/
+
+#define FILEOP_UPDATE_INTERVAL 2
+#define FILEOP_STALLING_INTERVAL 4
+#define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
+#define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
+
+/*** file scope type declarations ****************************************************************/
+
+/* This is a hard link cache */
+struct link
+{
+ const struct vfs_class *vfs;
+ dev_t dev;
+ ino_t ino;
+ short linkcount;
+ mode_t st_mode;
+ vfs_path_t *src_vpath;
+ vfs_path_t *dst_vpath;
+};
+
+/* Status of the destination file */
+typedef enum
+{
+ DEST_NONE = 0, /**< Not created */
+ DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */
+ DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */
+ DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */
+ DEST_FULL /**< Created, fully copied */
+} dest_status_t;
+
+/* Status of hard link creation */
+typedef enum
+{
+ HARDLINK_OK = 0, /**< Hardlink was created successfully */
+ HARDLINK_CACHED, /**< Hardlink was added to the cache */
+ HARDLINK_NOTLINK, /**< This is not a hard link */
+ HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */
+ HARDLINK_ERROR, /**< Hard link creation error */
+ HARDLINK_ABORT /**< Stop file operation after hardlink creation error */
+} hardlink_status_t;
+
+/*
+ * This array introduced to avoid translation problems. The former (op_names)
+ * is assumed to be nouns, suitable in dialog box titles; this one should
+ * contain whatever is used in prompt itself (i.e. in russian, it's verb).
+ * (I don't use spaces around the words, because someday they could be
+ * dropped, when widgets get smarter)
+ */
+
+/* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
+static const char *op_names1[] = {
+ N_("FileOperation|Copy"),
+ N_("FileOperation|Move"),
+ N_("FileOperation|Delete")
+};
+
+/*
+ * These are formats for building a prompt. Parts encoded as follows:
+ * %o - operation from op_names1
+ * %f - file/files or files/directories, as appropriate
+ * %m - "with source mask" or question mark for delete
+ * %s - source name (truncated)
+ * %d - number of marked files
+ * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
+ */
+/* xgettext:no-c-format */
+static const char *one_format = N_("%o %f%n\"%s\"%m");
+/* xgettext:no-c-format */
+static const char *many_format = N_("%o %d %f%m");
+
+static const char *prompt_parts[] = {
+ N_("file"),
+ N_("files"),
+ N_("directory"),
+ N_("directories"),
+ N_("files/directories"),
+ /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
+ N_(" with source mask:")
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* the hard link cache */
+static GSList *linklist = NULL;
+
+/* the files-to-be-erased list */
+static GQueue *erase_list = NULL;
+
+/*
+ * In copy_dir_dir we use two additional single linked lists: The first -
+ * variable name 'parent_dirs' - holds information about already copied
+ * directories and is used to detect cyclic symbolic links.
+ * The second ('dest_dirs' below) holds information about just created
+ * target directories and is used to detect when an directory is copied
+ * into itself (we don't want to copy infinitely).
+ * Both lists don't use the linkcount and name structure members of struct
+ * link.
+ */
+static GSList *dest_dirs = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dirsize_status_locate_buttons (dirsize_status_msg_t * dsm)
+{
+ status_msg_t *sm = STATUS_MSG (dsm);
+ Widget *wd = WIDGET (sm->dlg);
+ int y, x;
+ WRect r;
+
+ y = wd->rect.y + 5;
+ x = wd->rect.x;
+
+ if (!dsm->allow_skip)
+ {
+ /* single button: "Abort" */
+ x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2;
+ r = dsm->abort_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->abort_button, &r);
+ }
+ else
+ {
+ /* two buttons: "Abort" and "Skip" */
+ int cols;
+
+ cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1;
+ x += (wd->rect.cols - cols) / 2;
+ r = dsm->abort_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->abort_button, &r);
+ x += dsm->abort_button->rect.cols + 1;
+ r = dsm->skip_button->rect;
+ r.y = y;
+ r.x = x;
+ widget_set_size_rect (dsm->skip_button, &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status)
+{
+ char *s, *q;
+ const char *fnsource;
+
+ *status = FILE_CONT;
+
+ s = g_strdup (src);
+
+ /* We remove \n from the filename since regex routines would use \n as an anchor */
+ /* this is just to be allowed to maniupulate file names with \n on it */
+ for (q = s; *q != '\0'; q++)
+ if (*q == '\n')
+ *q = ' ';
+
+ fnsource = x_basename (s);
+
+ if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
+ {
+ q = NULL;
+ *status = FILE_SKIP;
+ }
+ else
+ {
+ q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
+ if (ctx->search_handle->error != MC_SEARCH_E_OK)
+ {
+ if (ctx->search_handle->error_str != NULL)
+ message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
+
+ *status = FILE_ABORT;
+ }
+ }
+
+ MC_PTR_FREE (s);
+
+ if (*status == FILE_CONT)
+ {
+ char *repl_dest;
+
+ repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
+ if (ctx->search_handle->error == MC_SEARCH_E_OK)
+ s = mc_build_filename (repl_dest, q, (char *) NULL);
+ else
+ {
+ if (ctx->search_handle->error_str != NULL)
+ message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
+
+ *status = FILE_ABORT;
+ }
+
+ g_free (repl_dest);
+ }
+
+ g_free (q);
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+free_link (void *data)
+{
+ struct link *lp = (struct link *) data;
+
+ vfs_path_free (lp->src_vpath, TRUE);
+ vfs_path_free (lp->dst_vpath, TRUE);
+ g_free (lp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void *
+free_erase_list (GQueue * lp)
+{
+ if (lp != NULL)
+ g_queue_free_full (lp, free_link);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void *
+free_linklist (GSList * lp)
+{
+ g_slist_free_full (lp, free_link);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const struct link *
+is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb)
+{
+ const struct vfs_class *class;
+ ino_t ino = sb->st_ino;
+ dev_t dev = sb->st_dev;
+
+ class = vfs_path_get_last_path_vfs (vpath);
+
+ for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
+ {
+ const struct link *lnk = (const struct link *) lp->data;
+
+ if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
+ return lnk;
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check and made hardlink
+ *
+ * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
+ * and a hardlink was successfully made
+ */
+
+static hardlink_status_t
+check_hardlinks (const vfs_path_t * src_vpath, const struct stat *src_stat,
+ const vfs_path_t * dst_vpath, gboolean * skip_all)
+{
+ struct link *lnk;
+ ino_t ino = src_stat->st_ino;
+ dev_t dev = src_stat->st_dev;
+
+ if (src_stat->st_nlink < 2)
+ return HARDLINK_NOTLINK;
+ if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
+ return HARDLINK_UNSUPPORTED;
+
+ lnk = (struct link *) is_in_linklist (linklist, src_vpath, src_stat);
+ if (lnk != NULL)
+ {
+ int stat_result;
+ struct stat link_stat;
+
+ stat_result = mc_stat (lnk->src_vpath, &link_stat);
+
+ if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
+ {
+ const struct vfs_class *lp_name_class;
+ const struct vfs_class *my_vfs;
+
+ lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
+ my_vfs = vfs_path_get_last_path_vfs (src_vpath);
+
+ if (lp_name_class == my_vfs)
+ {
+ const struct vfs_class *p_class, *dst_name_class;
+
+ dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
+ p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
+
+ if (dst_name_class == p_class)
+ {
+ gboolean ok;
+
+ while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*skip_all)
+ {
+ FileProgressStatus status;
+
+ status =
+ file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
+ vfs_path_as_str (lnk->dst_vpath));
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+ if (status == FILE_RETRY)
+ continue;
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ break;
+ }
+
+ /* if stat() finished unsuccessfully, don't try to create link */
+ if (!ok)
+ return HARDLINK_ERROR;
+
+ while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*skip_all)
+ {
+ FileProgressStatus status;
+
+ status =
+ file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
+ vfs_path_as_str (dst_vpath));
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+ if (status == FILE_RETRY)
+ continue;
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ break;
+ }
+
+ /* Success? */
+ return (ok ? HARDLINK_OK : HARDLINK_ERROR);
+ }
+ }
+ }
+
+ if (!*skip_all)
+ {
+ FileProgressStatus status;
+
+ /* Message w/o "Retry" action.
+ *
+ * FIXME: Can't say what errno is here. Define it and don't display.
+ *
+ * file_error() displays a message with text representation of errno
+ * and the string passed to file_error() should provide the format "%s"
+ * for that at end (see previous file_error() call for the reference).
+ * But if format for errno isn't provided, it is safe, because C standard says:
+ * "If the format is exhausted while arguments remain, the excess arguments
+ * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
+ * section 7.19.6.1, paragraph 2).
+ *
+ */
+ errno = 0;
+ status =
+ file_error (FALSE, _("Cannot create target hardlink \"%s\""),
+ vfs_path_as_str (dst_vpath));
+
+ if (status == FILE_ABORT)
+ return HARDLINK_ABORT;
+
+ if (status == FILE_SKIPALL)
+ *skip_all = TRUE;
+ }
+
+ return HARDLINK_ERROR;
+ }
+
+ lnk = g_try_new (struct link, 1);
+ if (lnk != NULL)
+ {
+ lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
+ lnk->ino = ino;
+ lnk->dev = dev;
+ lnk->linkcount = 0;
+ lnk->st_mode = 0;
+ lnk->src_vpath = vfs_path_clone (src_vpath);
+ lnk->dst_vpath = vfs_path_clone (dst_vpath);
+
+ linklist = g_slist_prepend (linklist, lnk);
+ }
+
+ return HARDLINK_CACHED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
+ * Try to make a stable symlink if the option "stable symlink" was
+ * set in the file mask dialog.
+ * If dst_path is an existing symlink it will be deleted silently
+ * (upper levels take already care of existing files at dst_vpath).
+ */
+
+static FileProgressStatus
+make_symlink (file_op_context_t * ctx, const vfs_path_t * src_vpath, const vfs_path_t * dst_vpath)
+{
+ const char *src_path;
+ const char *dst_path;
+ char link_target[MC_MAXPATHLEN];
+ int len;
+ FileProgressStatus return_status;
+ struct stat dst_stat;
+ gboolean dst_is_symlink;
+ vfs_path_t *link_target_vpath = NULL;
+
+ src_path = vfs_path_as_str (src_vpath);
+ dst_path = vfs_path_as_str (dst_vpath);
+
+ dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
+
+ retry_src_readlink:
+ len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
+ if (len < 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_src_readlink;
+ }
+ goto ret;
+ }
+
+ link_target[len] = '\0';
+
+ if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("Cannot make stable symlinks across "
+ "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
+ ctx->stable_symlinks = FALSE;
+ }
+
+ if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
+ {
+ const char *r;
+
+ r = strrchr (src_path, PATH_SEP);
+ if (r != NULL)
+ {
+ size_t slen;
+ GString *p;
+ vfs_path_t *q;
+
+ slen = r - src_path + 1;
+
+ p = g_string_sized_new (slen + len);
+ g_string_append_len (p, src_path, slen);
+
+ if (g_path_is_absolute (dst_path))
+ q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
+ else
+ q = vfs_path_build_filename (p->str, dst_path, (char *) NULL);
+
+ if (vfs_path_tokens_count (q) > 1)
+ {
+ char *s = NULL;
+ vfs_path_t *tmp_vpath1, *tmp_vpath2;
+
+ g_string_append_len (p, link_target, len);
+ tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
+ tmp_vpath2 = vfs_path_from_str (p->str);
+ s = diff_two_paths (tmp_vpath1, tmp_vpath2);
+ vfs_path_free (tmp_vpath2, TRUE);
+ vfs_path_free (tmp_vpath1, TRUE);
+ g_strlcpy (link_target, s != NULL ? s : p->str, sizeof (link_target));
+ g_free (s);
+ }
+
+ g_string_free (p, TRUE);
+ vfs_path_free (q, TRUE);
+ }
+ }
+ link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
+
+ retry_dst_symlink:
+ if (mc_symlink (link_target_vpath, dst_vpath) == 0)
+ {
+ /* Success */
+ return_status = FILE_CONT;
+ goto ret;
+ }
+ /*
+ * if dst_exists, it is obvious that this had failed.
+ * We can delete the old symlink and try again...
+ */
+ if (dst_is_symlink && mc_unlink (dst_vpath) == 0
+ && mc_symlink (link_target_vpath, dst_vpath) == 0)
+ {
+ /* Success */
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_symlink;
+ }
+
+ ret:
+ vfs_path_free (link_target_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * do_compute_dir_size:
+ *
+ * Computes the number of bytes used by the files in a directory
+ */
+
+static FileProgressStatus
+do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
+ size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
+ mc_stat_fn stat_func)
+{
+ static gint64 timestamp = 0;
+ /* update with 25 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 25;
+
+ status_msg_t *sm = STATUS_MSG (dsm);
+ int res;
+ struct stat s;
+ DIR *dir;
+ struct vfs_dirent *dirent;
+ FileProgressStatus ret = FILE_CONT;
+
+ (*dir_count)++;
+
+ dir = mc_opendir (dirname_vpath);
+ if (dir == NULL)
+ return ret;
+
+ while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
+ {
+ vfs_path_t *tmp_vpath;
+
+ if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
+ continue;
+
+ tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
+
+ res = stat_func (tmp_vpath, &s);
+ if (res == 0)
+ {
+ if (S_ISDIR (s.st_mode))
+ ret =
+ do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
+ stat_func);
+ else
+ {
+ ret = FILE_CONT;
+
+ (*ret_marked)++;
+ *ret_total += (uintmax_t) s.st_size;
+ }
+
+ if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
+ {
+ dsm->dirname_vpath = tmp_vpath;
+ dsm->dir_count = *dir_count;
+ dsm->total_size = *ret_total;
+ ret = sm->update (sm);
+ }
+ }
+
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ mc_closedir (dir);
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_compute_totals:
+ *
+ * compute the number of files and the number of bytes
+ * used up by the whole selection, recursing directories
+ * as required. In addition, it checks to see if it will
+ * overwrite any files by doing the copy.
+ */
+
+static FileProgressStatus
+panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count,
+ uintmax_t * ret_total, gboolean follow_symlinks)
+{
+ int i;
+ size_t dir_count = 0;
+ mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ const file_entry_t *fe = &panel->dir.list[i];
+ const struct stat *s;
+
+ if (fe->f.marked == 0)
+ continue;
+
+ s = &fe->st;
+
+ if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0))
+ {
+ vfs_path_t *p;
+ FileProgressStatus status;
+
+ p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
+ vfs_path_free (p, TRUE);
+
+ if (status != FILE_CONT)
+ return status;
+ }
+ else
+ {
+ (*ret_count)++;
+ *ret_total += (uintmax_t) s->st_size;
+ }
+ }
+
+ return FILE_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Initialize variables for progress bars */
+static FileProgressStatus
+panel_operate_init_totals (const WPanel * panel, const vfs_path_t * source,
+ const struct stat *source_stat, file_op_context_t * ctx,
+ gboolean compute_totals, filegui_dialog_type_t dialog_type)
+{
+ FileProgressStatus status;
+
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ return FILE_CONT;
+#endif
+
+ if (verbose && compute_totals)
+ {
+ dirsize_status_msg_t dsm;
+ gboolean stale_link = FALSE;
+
+ memset (&dsm, 0, sizeof (dsm));
+ dsm.allow_skip = TRUE;
+ status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
+ dirsize_status_update_cb, dirsize_status_deinit_cb);
+
+ ctx->progress_count = 0;
+ ctx->progress_bytes = 0;
+
+ if (source == NULL)
+ status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
+ ctx->follow_links);
+ else if (S_ISDIR (source_stat->st_mode)
+ || (ctx->follow_links
+ && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link)
+ && !stale_link))
+ {
+ size_t dir_count = 0;
+
+ status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->progress_count,
+ &ctx->progress_bytes, ctx->stat_func);
+ }
+ else
+ {
+ ctx->progress_count++;
+ ctx->progress_bytes += (uintmax_t) source_stat->st_size;
+ status = FILE_CONT;
+ }
+
+ status_msg_deinit (STATUS_MSG (&dsm));
+
+ ctx->progress_totals_computed = (status == FILE_CONT);
+
+ if (status == FILE_SKIP)
+ status = FILE_CONT;
+ }
+ else
+ {
+ status = FILE_CONT;
+ ctx->progress_count = panel->marked;
+ ctx->progress_bytes = panel->total;
+ ctx->progress_totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM;
+ }
+
+ /* destroy already created UI for single file rename operation */
+ file_op_context_destroy_ui (ctx);
+
+ file_op_context_create_ui (ctx, TRUE, dialog_type);
+
+ return status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add)
+{
+ gint64 tv_current;
+ static gint64 tv_start = -1;
+
+ tctx->progress_count++;
+ tctx->progress_bytes += (uintmax_t) add;
+
+ tv_current = g_get_monotonic_time ();
+
+ if (tv_start < 0)
+ tv_start = tv_current;
+
+ if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
+ {
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
+ }
+
+ tv_start = tv_current;
+ }
+
+ return check_progress_buttons (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
+{
+ char *msg;
+ int result = 0;
+ const char *head_msg;
+ int width_a, width_b, width;
+
+ head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
+
+ width_a = str_term_width1 (a);
+ width_b = str_term_width1 (b);
+ width = COLS - 8;
+
+ if (width_a > width)
+ {
+ if (width_b > width)
+ {
+ char *s;
+
+ s = g_strndup (str_trunc (a, width), width);
+ b = str_trunc (b, width);
+ msg = g_strdup_printf (fmt, s, b);
+ g_free (s);
+ }
+ else
+ {
+ a = str_trunc (a, width);
+ msg = g_strdup_printf (fmt, a, b);
+ }
+ }
+ else
+ {
+ if (width_b > width)
+ b = str_trunc (b, width);
+
+ msg = g_strdup_printf (fmt, a, b);
+ }
+
+ result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
+ g_free (msg);
+ do_refresh ();
+
+ return (result == 1) ? FILE_ABORT : FILE_SKIP;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+warn_same_file (const char *fmt, const char *a, const char *b)
+{
+#ifdef ENABLE_BACKGROUND
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_warn_same_file;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
+#endif
+ return real_warn_same_file (Foreground, fmt, a, b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst,
+ FileProgressStatus * status)
+{
+ if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino)
+ return FALSE;
+
+ if (S_ISDIR (ast->st_mode))
+ *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b);
+ else
+ *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+get_times (const struct stat *sb, mc_timesbuf_t * times)
+{
+#ifdef HAVE_UTIMENSAT
+ (*times)[0] = sb->st_atim;
+ (*times)[1] = sb->st_mtim;
+#else
+ times->actime = sb->st_atime;
+ times->modtime = sb->st_mtime;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Query/status report routines */
+
+static FileProgressStatus
+real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error)
+{
+ int result;
+ const char *msg;
+
+ msg = mode == Foreground ? MSG_ERROR : _("Background process error");
+
+ if (allow_retry)
+ result =
+ query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"),
+ _("&Abort"));
+ else
+ result = query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("Ski&p all"), _("&Abort"));
+
+ switch (result)
+ {
+ case 0:
+ do_refresh ();
+ return FILE_SKIP;
+
+ case 1:
+ do_refresh ();
+ return FILE_SKIPALL;
+
+ case 2:
+ if (allow_retry)
+ {
+ do_refresh ();
+ return FILE_RETRY;
+ }
+ MC_FALLTHROUGH;
+
+ case 3:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s)
+{
+ if (ctx->recursive_result < RECURSIVE_ALWAYS)
+ {
+ const char *msg;
+ char *text;
+
+ msg = mode == Foreground
+ ? _("Directory \"%s\" not empty.\nDelete it recursively?")
+ : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
+ text = g_strdup_printf (msg, path_trunc (s, 30));
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ ctx->recursive_result =
+ query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
+ _("Non&e"), _("&Abort"));
+ g_free (text);
+
+ if (ctx->recursive_result != RECURSIVE_ABORT)
+ do_refresh ();
+ }
+
+ switch (ctx->recursive_result)
+ {
+ case RECURSIVE_YES:
+ case RECURSIVE_ALWAYS:
+ return FILE_CONT;
+
+ case RECURSIVE_NO:
+ case RECURSIVE_NEVER:
+ return FILE_SKIP;
+
+ case RECURSIVE_ABORT:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static FileProgressStatus
+do_file_error (gboolean allow_retry, const char *str)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (enum OperationMode, gboolean, const char *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_do_file_error;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str);
+ else
+ return real_do_file_error (Foreground, allow_retry, str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_recursive (file_op_context_t * ctx, const char *s)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = real_query_recursive;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, ctx, 1, strlen (s), s);
+ else
+ return real_query_recursive (ctx, Foreground, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst,
+ struct stat *dst_stat)
+{
+/* *INDENT-OFF* */
+ union
+ {
+ void *p;
+ FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
+ struct stat *, const char *, struct stat *);
+ } pntr;
+/* *INDENT-ON* */
+
+ pntr.f = file_progress_real_query_replace;
+
+ if (mc_global.we_are_background)
+ return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat,
+ strlen (dst), dst, sizeof (struct stat), dst_stat);
+ else
+ return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
+}
+
+#else
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+do_file_error (gboolean allow_retry, const char *str)
+{
+ return real_do_file_error (Foreground, allow_retry, str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_recursive (file_op_context_t * ctx, const char *s)
+{
+ return real_query_recursive (ctx, Foreground, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst,
+ struct stat *dst_stat)
+{
+ return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
+}
+
+#endif /* !ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/** Report error with two files */
+
+static FileProgressStatus
+files_error (const char *format, const char *file1, const char *file2)
+{
+ char buf[BUF_MEDIUM];
+ char *nfile1 = g_strdup (path_trunc (file1, 15));
+ char *nfile2 = g_strdup (path_trunc (file2, 15));
+
+ g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
+
+ g_free (nfile1);
+ g_free (nfile2);
+
+ return do_file_error (TRUE, buf);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ gint64 tv_current, gint64 tv_transfer_start, off_t file_size,
+ off_t file_part)
+{
+ gint64 dt;
+
+ /* Update rotating dash after some time */
+ rotate_dash (TRUE);
+
+ /* Compute ETA */
+ dt = (tv_current - tv_transfer_start) / G_USEC_PER_SEC;
+
+ if (file_part == 0)
+ ctx->eta_secs = 0.0;
+ else
+ ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt;
+
+ /* Compute BPS rate */
+ ctx->bps_time = MAX (1, dt);
+ ctx->bps = file_part / ctx->bps_time;
+
+ /* Compute total ETA and BPS */
+ if (ctx->progress_bytes != 0)
+ {
+ uintmax_t remain_bytes;
+
+ remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
+#if 1
+ {
+ gint64 total_secs;
+
+ total_secs = (tv_current - tctx->transfer_start) / G_USEC_PER_SEC;
+ total_secs = MAX (1, total_secs);
+
+ tctx->bps = tctx->copied_bytes / total_secs;
+ tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
+ }
+#else
+ /* broken on lot of little files */
+ tctx->bps_count++;
+ tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
+ tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
+#endif
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_remove_file (file_op_context_t * ctx, const vfs_path_t * vpath, FileProgressStatus * status)
+{
+ while (mc_unlink (vpath) != 0 && !ctx->skip_all)
+ {
+ *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
+ if (*status == FILE_RETRY)
+ continue;
+ if (*status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Move routines */
+
+/**
+ * Move single file or one of many files from one location to another.
+ *
+ * @panel pointer to panel in case of single file, NULL otherwise
+ * @tctx file operation total context object
+ * @ctx file operation context object
+ * @s source file name
+ * @d destination file name
+ *
+ * @return operation result
+ */
+static FileProgressStatus
+move_file_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d)
+{
+ struct stat src_stat, dst_stat;
+ FileProgressStatus return_status = FILE_CONT;
+ gboolean copy_done = FALSE;
+ gboolean old_ask_overwrite;
+ vfs_path_t *src_vpath, *dst_vpath;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ /* FIXME: do we really need to check buttons in case of single file? */
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ while (mc_lstat (src_vpath, &src_stat) != 0)
+ {
+ /* Source doesn't exist */
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+
+ if (mc_lstat (dst_vpath, &dst_stat) == 0)
+ {
+ if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
+ goto ret;
+
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
+ do_refresh ();
+ return_status = FILE_SKIP;
+ goto ret;
+ }
+
+ if (confirm_overwrite)
+ {
+ return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+ /* Ok to overwrite */
+ }
+
+ if (!ctx->do_append)
+ {
+ if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
+ {
+ return_status = make_symlink (ctx, src_vpath, dst_vpath);
+ if (return_status == FILE_CONT)
+ {
+ if (ctx->preserve)
+ {
+ mc_timesbuf_t times;
+
+ get_times (&src_stat, &times);
+ mc_utime (dst_vpath, &times);
+ }
+ goto retry_src_remove;
+ }
+ goto ret;
+ }
+
+ if (mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ /* FIXME: do we really need to update progress in case of single file? */
+ return_status = progress_update_one (tctx, ctx, src_stat.st_size);
+ goto ret;
+ }
+ }
+#if 0
+ /* Comparison to EXDEV seems not to work in nfs if you're moving from
+ one nfs to the same, but on the server it is on two different
+ filesystems. Then nfs returns EIO instead of EXDEV.
+ Hope it will not hurt if we always in case of error try to copy/delete. */
+ else
+ errno = EXDEV; /* Hack to copy (append) the file and then delete it */
+
+ if (errno != EXDEV)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_rename;
+ }
+
+ goto ret;
+ }
+#endif
+
+ /* Failed rename -> copy the file instead */
+ if (panel != NULL)
+ {
+ /* In case of single file, calculate totals. In case of many files,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_ONE_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ old_ask_overwrite = tctx->ask_overwrite;
+ tctx->ask_overwrite = FALSE;
+ return_status = copy_file_file (tctx, ctx, s, d);
+ tctx->ask_overwrite = old_ask_overwrite;
+ if (return_status != FILE_CONT)
+ goto ret;
+
+ copy_done = TRUE;
+
+ /* FIXME: there is no need to update progress and check buttons
+ at the finish of single file operation. */
+ if (panel == NULL)
+ {
+ file_progress_show_source (ctx, NULL);
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ retry_src_remove:
+ if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
+ goto ret;
+
+ if (!copy_done)
+ return_status = progress_update_one (tctx, ctx, src_stat.st_size);
+
+ ret:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+
+ return return_status;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Erase routines */
+/** Don't update progress status if progress_count==NULL */
+
+static FileProgressStatus
+erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ struct stat buf;
+ FileProgressStatus return_status;
+
+ /* check buttons if deleting info was changed */
+ if (file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count))
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+ }
+
+ if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
+ {
+ /* ignore, most likely the mc_unlink fails, too */
+ buf.st_size = 0;
+ }
+
+ if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
+ return FILE_ABORT;
+
+ if (tctx->progress_count == 0)
+ return FILE_CONT;
+
+ return check_progress_buttons (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+try_erase_dir (file_op_context_t * ctx, const char *dir)
+{
+ FileProgressStatus return_status = FILE_CONT;
+
+ while (my_rmdir (dir) != 0 && !ctx->skip_all)
+ {
+ return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status != FILE_RETRY)
+ break;
+ }
+
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ Recursive remove of files
+ abort->cancel stack
+ skip ->warn every level, gets default
+ skipall->remove as much as possible
+*/
+static FileProgressStatus
+recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ struct vfs_dirent *next;
+ DIR *reading;
+ const char *s;
+ FileProgressStatus return_status = FILE_CONT;
+
+ reading = mc_opendir (vpath);
+ if (reading == NULL)
+ return FILE_RETRY;
+
+ while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
+ {
+ vfs_path_t *tmp_vpath;
+ struct stat buf;
+
+ if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
+ continue;
+
+ tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
+ if (mc_lstat (tmp_vpath, &buf) != 0)
+ {
+ mc_closedir (reading);
+ vfs_path_free (tmp_vpath, TRUE);
+ return FILE_RETRY;
+ }
+ if (S_ISDIR (buf.st_mode))
+ return_status = recursive_erase (tctx, ctx, tmp_vpath);
+ else
+ return_status = erase_file (tctx, ctx, tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ mc_closedir (reading);
+
+ if (return_status == FILE_ABORT)
+ return FILE_ABORT;
+
+ s = vfs_path_as_str (vpath);
+
+ file_progress_show_deleting (ctx, s, NULL);
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ return try_erase_dir (ctx, s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check if directory is empty or not.
+ *
+ * @param vpath directory handler
+ *
+ * @returns -1 on error,
+ * 1 if there are no entries besides "." and ".." in the directory path points to,
+ * 0 else.
+ *
+ * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
+ * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
+ * in FISH) don't return "." and ".." entries.
+ */
+static int
+check_dir_is_empty (const vfs_path_t * vpath)
+{
+ DIR *dir;
+ struct vfs_dirent *d;
+ int i = 1;
+
+ dir = mc_opendir (vpath);
+ if (dir == NULL)
+ return -1;
+
+ for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
+ if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
+ {
+ i = 0;
+ break;
+ }
+
+ mc_closedir (dir);
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
+{
+ const char *s;
+
+ s = vfs_path_as_str (vpath);
+
+ file_progress_show_deleting (ctx, s, NULL);
+ file_progress_show_count (ctx, count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ if (check_dir_is_empty (vpath) != 1)
+ return FILE_CONT;
+
+ /* not empty or error */
+ return try_erase_dir (ctx, s);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+erase_dir_after_copy (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const vfs_path_t * vpath, FileProgressStatus * status)
+{
+ if (ctx->erase_at_end && erase_list != NULL)
+ {
+ /* Reset progress count before delete to avoid counting files twice */
+ tctx->progress_count = tctx->prev_progress_count;
+
+ while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
+ {
+ struct link *lp;
+
+ lp = (struct link *) g_queue_pop_head (erase_list);
+
+ if (S_ISDIR (lp->st_mode))
+ *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
+ else
+ *status = erase_file (tctx, ctx, lp->src_vpath);
+
+ free_link (lp);
+ }
+
+ /* Save progress counter before move next directory */
+ tctx->prev_progress_count = tctx->progress_count;
+ }
+
+ erase_dir_iff_empty (ctx, vpath, tctx->progress_count);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Move single directory or one of many directories from one location to another.
+ *
+ * @panel pointer to panel in case of single directory, NULL otherwise
+ * @tctx file operation total context object
+ * @ctx file operation context object
+ * @s source directory name
+ * @d destination directory name
+ *
+ * @return operation result
+ */
+static FileProgressStatus
+do_move_dir_dir (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d)
+{
+ struct stat src_stat, dst_stat;
+ FileProgressStatus return_status = FILE_CONT;
+ gboolean move_over = FALSE;
+ gboolean dstat_ok;
+ vfs_path_t *src_vpath, *dst_vpath;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ /* FIXME: do we really need to check buttons in case of single directory? */
+ if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret_fast;
+ }
+
+ mc_refresh ();
+
+ mc_stat (src_vpath, &src_stat);
+
+ dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
+
+ if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
+ goto ret_fast;
+
+ if (!dstat_ok)
+ ; /* destination doesn't exist */
+ else if (!ctx->dive_into_subdirs)
+ move_over = TRUE;
+ else
+ {
+ vfs_path_t *tmp;
+
+ tmp = dst_vpath;
+ dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
+ vfs_path_free (tmp, TRUE);
+ }
+
+ d = vfs_path_as_str (dst_vpath);
+
+ /* Check if the user inputted an existing dir */
+ retry_dst_stat:
+ if (mc_stat (dst_vpath, &dst_stat) == 0)
+ {
+ if (move_over)
+ {
+ if (panel != NULL)
+ {
+ /* In case of single directory, calculate totals. In case of many directories,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_MULTI_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
+
+ if (return_status != FILE_CONT)
+ goto ret;
+ goto oktoret;
+ }
+ else if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ if (S_ISDIR (dst_stat.st_mode))
+ return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
+ else
+ return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_stat;
+ }
+
+ goto ret_fast;
+ }
+
+ retry_rename:
+ if (mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ if (errno != EXDEV)
+ {
+ if (!ctx->skip_all)
+ {
+ return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_rename;
+ }
+ goto ret;
+ }
+
+ /* Failed because of filesystem boundary -> copy dir instead */
+ if (panel != NULL)
+ {
+ /* In case of single directory, calculate totals. In case of many directories,
+ totals are calculated already. */
+ return_status =
+ panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
+ FILEGUI_DIALOG_MULTI_ITEM);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
+
+ if (return_status != FILE_CONT)
+ goto ret;
+
+ oktoret:
+ /* FIXME: there is no need to update progress and check buttons
+ at the finish of single directory operation. */
+ if (panel == NULL)
+ {
+ file_progress_show_source (ctx, NULL);
+ file_progress_show_target (ctx, NULL);
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ goto ret;
+ }
+
+ mc_refresh ();
+
+ erase_dir_after_copy (tctx, ctx, src_vpath, &return_status);
+
+ ret:
+ erase_list = free_erase_list (erase_list);
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* {{{ Panel operate routines */
+
+/**
+ * Return currently selected entry name or the name of the first marked
+ * entry if there is one.
+ */
+
+static const char *
+panel_get_file (const WPanel * panel)
+{
+ if (get_current_type () == view_tree)
+ {
+ WTree *tree;
+ const vfs_path_t *selected_name;
+
+ tree = (WTree *) get_panel_widget (get_current_index ());
+ selected_name = tree_selected_name (tree);
+ return vfs_path_as_str (selected_name);
+ }
+
+ if (panel->marked != 0)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ return panel->dir.list[i].fname->str;
+ }
+
+ return panel_current_entry (panel)->fname->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+check_single_entry (const WPanel * panel, gboolean force_single, struct stat *src_stat)
+{
+ const char *source;
+ gboolean ok;
+
+ if (force_single)
+ source = panel_current_entry (panel)->fname->str;
+ else
+ source = panel_get_file (panel);
+
+ ok = !DIR_IS_DOTDOT (source);
+
+ if (!ok)
+ message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
+ else
+ {
+ vfs_path_t *source_vpath;
+
+ source_vpath = vfs_path_from_str (source);
+
+ /* Update stat to get actual info */
+ ok = mc_lstat (source_vpath, src_stat) == 0;
+ if (!ok)
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
+ path_trunc (source, 30), unix_error_string (errno));
+
+ /* Directory was changed outside MC. Reload it forced */
+ if (!panel->is_panelized)
+ {
+ panel_update_flags_t flags = UP_RELOAD;
+
+ /* don't update panelized panel */
+ if (get_other_type () == view_listing && other_panel->is_panelized)
+ flags |= UP_ONLY_CURRENT;
+
+ update_panels (flags, UP_KEEPSEL);
+ }
+ }
+
+ vfs_path_free (source_vpath, TRUE);
+ }
+
+ return ok ? source : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Generate user prompt for panel operation.
+ * src_stat must be not NULL for single source, and NULL for multiple sources
+ */
+
+static char *
+panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
+ const struct stat *src_stat)
+{
+ char *sp;
+ char *format_string;
+ const char *cp;
+
+ static gboolean i18n_flag = FALSE;
+ if (!i18n_flag)
+ {
+ size_t i;
+
+ for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
+ op_names1[i] = Q_ (op_names1[i]);
+
+#ifdef ENABLE_NLS
+ for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
+ prompt_parts[i] = _(prompt_parts[i]);
+
+ one_format = _(one_format);
+ many_format = _(many_format);
+#endif /* ENABLE_NLS */
+ i18n_flag = TRUE;
+ }
+
+ /* Possible prompts:
+ * OP_COPY:
+ * "Copy file \"%s\" with source mask:"
+ * "Copy %d files with source mask:"
+ * "Copy directory \"%s\" with source mask:"
+ * "Copy %d directories with source mask:"
+ * "Copy %d files/directories with source mask:"
+ * OP_MOVE:
+ * "Move file \"%s\" with source mask:"
+ * "Move %d files with source mask:"
+ * "Move directory \"%s\" with source mask:"
+ * "Move %d directories with source mask:"
+ * "Move %d files/directories with source mask:"
+ * OP_DELETE:
+ * "Delete file \"%s\"?"
+ * "Delete %d files?"
+ * "Delete directory \"%s\"?"
+ * "Delete %d directories?"
+ * "Delete %d files/directories?"
+ */
+
+ cp = (src_stat != NULL ? one_format : many_format);
+
+ /* 1. Substitute %o */
+ format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
+
+ /* 2. Substitute %n */
+ cp = operation == OP_DELETE ? "\n" : " ";
+ sp = format_string;
+ format_string = str_replace_all (sp, "%n", cp);
+ g_free (sp);
+
+ /* 3. Substitute %f */
+ if (src_stat != NULL)
+ cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
+ else if (panel->marked == panel->dirs_marked)
+ cp = prompt_parts[3];
+ else
+ cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
+
+ sp = format_string;
+ format_string = str_replace_all (sp, "%f", cp);
+ g_free (sp);
+
+ /* 4. Substitute %m */
+ cp = operation == OP_DELETE ? "?" : prompt_parts[5];
+ sp = format_string;
+ format_string = str_replace_all (sp, "%m", cp);
+ g_free (sp);
+
+ return format_string;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+do_confirm_copy_move (const WPanel * panel, gboolean force_single, const char *source,
+ struct stat *src_stat, file_op_context_t * ctx, gboolean * do_bg)
+{
+ const char *tmp_dest_dir;
+ char *dest_dir;
+ char *format;
+ char *ret;
+
+ /* Forced single operations default to the original name */
+ if (force_single)
+ tmp_dest_dir = source;
+ else if (get_other_type () == view_listing)
+ tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
+ else
+ tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
+
+ /*
+ * Add trailing backslash only when do non-local ops.
+ * It saves user from occasional file renames (when destination
+ * dir is deleted)
+ */
+ if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
+ && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
+ {
+ /* add trailing separator */
+ dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
+ }
+ else
+ {
+ /* just copy */
+ dest_dir = g_strdup (tmp_dest_dir);
+ }
+
+ if (dest_dir == NULL)
+ return NULL;
+
+ if (source == NULL)
+ src_stat = NULL;
+
+ /* Generate confirmation prompt */
+ format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
+
+ ret = file_mask_dialog (ctx, source != NULL, format,
+ source != NULL ? source : (const void *) &panel->marked, dest_dir,
+ do_bg);
+
+ g_free (format);
+ g_free (dest_dir);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+do_confirm_erase (const WPanel * panel, const char *source, struct stat *src_stat)
+{
+ int i;
+ char *format;
+ char fmd_buf[BUF_MEDIUM];
+
+ if (source == NULL)
+ src_stat = NULL;
+
+ /* Generate confirmation prompt */
+ format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
+
+ if (source == NULL)
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
+ else
+ {
+ const int fmd_xlen = 64;
+
+ i = fmd_xlen - str_term_width1 (format) - 4;
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
+ }
+
+ g_free (format);
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
+
+ return (i == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+operate_single_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src, struct stat *src_stat, const char *dest,
+ filegui_dialog_type_t dialog_type)
+{
+ FileProgressStatus value;
+ vfs_path_t *src_vpath;
+ gboolean is_file;
+
+ if (g_path_is_absolute (src))
+ src_vpath = vfs_path_from_str (src);
+ else
+ src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
+
+ is_file = !S_ISDIR (src_stat->st_mode);
+ /* Is link to directory? */
+ if (is_file)
+ {
+ gboolean is_link;
+
+ is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
+ is_file = !(is_link && ctx->follow_links);
+ }
+
+ if (ctx->operation == OP_DELETE)
+ {
+ value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
+ if (value == FILE_CONT)
+ {
+ if (is_file)
+ value = erase_file (tctx, ctx, src_vpath);
+ else
+ value = erase_dir (tctx, ctx, src_vpath);
+ }
+ }
+ else
+ {
+ char *temp;
+
+ src = vfs_path_as_str (src_vpath);
+
+ temp = build_dest (ctx, src, dest, &value);
+ if (temp != NULL)
+ {
+ dest = temp;
+
+ switch (ctx->operation)
+ {
+ case OP_COPY:
+ /* we use file_mask_op_follow_links only with OP_COPY */
+ ctx->stat_func (src_vpath, src_stat);
+
+ value =
+ panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
+ dialog_type);
+ if (value == FILE_CONT)
+ {
+ is_file = !S_ISDIR (src_stat->st_mode);
+ /* Is link to directory? */
+ if (is_file)
+ {
+ gboolean is_link;
+
+ is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
+ is_file = !(is_link && ctx->follow_links);
+ }
+
+ if (is_file)
+ value = copy_file_file (tctx, ctx, src, dest);
+ else
+ value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
+ }
+ break;
+
+ case OP_MOVE:
+#ifdef ENABLE_BACKGROUND
+ if (!mc_global.we_are_background)
+#endif
+ /* create UI to show confirmation dialog */
+ file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
+
+ if (is_file)
+ value = move_file_file (panel, tctx, ctx, src, dest);
+ else
+ value = do_move_dir_dir (panel, tctx, ctx, src, dest);
+ break;
+
+ default:
+ /* Unknown file operation */
+ abort ();
+ }
+
+ g_free (temp);
+ }
+ }
+
+ vfs_path_free (src_vpath, TRUE);
+
+ return value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FileProgressStatus
+operate_one_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src, struct stat *src_stat, const char *dest)
+{
+ FileProgressStatus value = FILE_CONT;
+ vfs_path_t *src_vpath;
+ gboolean is_file;
+
+ if (g_path_is_absolute (src))
+ src_vpath = vfs_path_from_str (src);
+ else
+ src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
+
+ is_file = !S_ISDIR (src_stat->st_mode);
+
+ if (ctx->operation == OP_DELETE)
+ {
+ if (is_file)
+ value = erase_file (tctx, ctx, src_vpath);
+ else
+ value = erase_dir (tctx, ctx, src_vpath);
+ }
+ else
+ {
+ char *temp;
+
+ src = vfs_path_as_str (src_vpath);
+
+ temp = build_dest (ctx, src, dest, &value);
+ if (temp != NULL)
+ {
+ dest = temp;
+
+ switch (ctx->operation)
+ {
+ case OP_COPY:
+ /* we use file_mask_op_follow_links only with OP_COPY */
+ ctx->stat_func (src_vpath, src_stat);
+ is_file = !S_ISDIR (src_stat->st_mode);
+
+ if (is_file)
+ value = copy_file_file (tctx, ctx, src, dest);
+ else
+ value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
+ dest_dirs = free_linklist (dest_dirs);
+ break;
+
+ case OP_MOVE:
+ if (is_file)
+ value = move_file_file (NULL, tctx, ctx, src, dest);
+ else
+ value = do_move_dir_dir (NULL, tctx, ctx, src, dest);
+ break;
+
+ default:
+ /* Unknown file operation */
+ abort ();
+ }
+
+ g_free (temp);
+ }
+ }
+
+ vfs_path_free (src_vpath, TRUE);
+
+ return value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static int
+end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
+{
+ int pid = ctx->pid;
+
+ (void) mode;
+ ctx->pid = 0;
+
+ unregister_task_with_pid (pid);
+ /* file_op_context_destroy(ctx); */
+ return 1;
+}
+#endif
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Is file symlink to directory or not.
+ *
+ * @param path file or directory
+ * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
+ * @param stale_link TRUE if file is stale link to directory
+ *
+ * @return TRUE if file symlink to directory, ELSE otherwise.
+ */
+gboolean
+file_is_symlink_to_dir (const vfs_path_t * vpath, struct stat * st, gboolean * stale_link)
+{
+ struct stat st2;
+ gboolean stale = FALSE;
+ gboolean res = FALSE;
+
+ if (st == NULL)
+ {
+ st = &st2;
+
+ if (mc_lstat (vpath, st) != 0)
+ goto ret;
+ }
+
+ if (S_ISLNK (st->st_mode))
+ {
+ struct stat st3;
+
+ stale = (mc_stat (vpath, &st3) != 0);
+
+ if (!stale)
+ res = (S_ISDIR (st3.st_mode) != 0);
+ }
+
+ ret:
+ if (stale_link != NULL)
+ *stale_link = stale;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src_path, const char *dst_path)
+{
+ uid_t src_uid = (uid_t) (-1);
+ gid_t src_gid = (gid_t) (-1);
+
+ int src_desc, dest_desc = -1;
+ mode_t src_mode = 0; /* The mode of the source file */
+ struct stat src_stat, dst_stat;
+ mc_timesbuf_t times;
+ gboolean dst_exists = FALSE, appending = FALSE;
+ off_t file_size = -1;
+ FileProgressStatus return_status, temp_status;
+ gint64 tv_transfer_start;
+ dest_status_t dst_status = DEST_NONE;
+ int open_flags;
+ vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
+ char *buf = NULL;
+
+ /* FIXME: We should not be using global variables! */
+ ctx->do_reget = 0;
+ return_status = FILE_RETRY;
+
+ dst_vpath = vfs_path_from_str (dst_path);
+ src_vpath = vfs_path_from_str (src_path);
+
+ file_progress_show_source (ctx, src_vpath);
+ file_progress_show_target (ctx, dst_vpath);
+
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ {
+ return_status = FILE_ABORT;
+ goto ret_fast;
+ }
+
+ mc_refresh ();
+
+ while (mc_stat (dst_vpath, &dst_stat) == 0)
+ {
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ continue;
+ }
+ goto ret_fast;
+ }
+
+ dst_exists = TRUE;
+ break;
+ }
+
+ while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+
+ if (return_status != FILE_RETRY)
+ goto ret_fast;
+ }
+
+ if (dst_exists)
+ {
+ /* Destination already exists */
+ if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
+ goto ret_fast;
+
+ /* Should we replace destination? */
+ if (tctx->ask_overwrite)
+ {
+ ctx->do_reget = 0;
+ return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
+ if (return_status != FILE_CONT)
+ goto ret_fast;
+ }
+ }
+
+ get_times (&src_stat, &times);
+
+ if (!ctx->do_append)
+ {
+ /* Check the hardlinks */
+ if (!ctx->follow_links)
+ {
+ switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
+ {
+ case HARDLINK_OK:
+ /* We have made a hardlink - no more processing is necessary */
+ return_status = FILE_CONT;
+ goto ret_fast;
+
+ case HARDLINK_ABORT:
+ return_status = FILE_ABORT;
+ goto ret_fast;
+
+ default:
+ break;
+ }
+ }
+
+ if (S_ISLNK (src_stat.st_mode))
+ {
+ return_status = make_symlink (ctx, src_vpath, dst_vpath);
+ if (return_status == FILE_CONT && ctx->preserve)
+ mc_utime (dst_vpath, &times);
+ goto ret_fast;
+ }
+
+ if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
+ || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
+ {
+ dev_t rdev = 0;
+
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ rdev = src_stat.st_rdev;
+#endif
+
+ while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
+ && !ctx->skip_all)
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ goto ret_fast;
+ }
+ /* Success */
+
+ while (ctx->preserve_uidgid
+ && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_SKIP)
+ break;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (temp_status != FILE_RETRY)
+ {
+ return_status = temp_status;
+ goto ret_fast;
+ }
+ }
+
+ while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_SKIP)
+ break;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (temp_status != FILE_RETRY)
+ {
+ return_status = temp_status;
+ goto ret_fast;
+ }
+ }
+
+ return_status = FILE_CONT;
+ mc_utime (dst_vpath, &times);
+ goto ret_fast;
+ }
+ }
+
+ tv_transfer_start = g_get_monotonic_time ();
+
+ while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
+ {
+ return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_SKIP)
+ break;
+ ctx->do_append = FALSE;
+ goto ret_fast;
+ }
+
+ if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
+ {
+ message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
+ ctx->do_reget = 0;
+ ctx->do_append = FALSE;
+ }
+
+ while (mc_fstat (src_desc, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ ctx->do_append = FALSE;
+ }
+ goto ret;
+ }
+
+ src_mode = src_stat.st_mode;
+ src_uid = src_stat.st_uid;
+ src_gid = src_stat.st_gid;
+ file_size = src_stat.st_size;
+
+ open_flags = O_WRONLY;
+ if (!dst_exists)
+ open_flags |= O_CREAT | O_EXCL;
+ else if (ctx->do_append)
+ open_flags |= O_APPEND;
+ else
+ open_flags |= O_CREAT | O_TRUNC;
+
+ while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
+ {
+ if (errno != EEXIST)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ ctx->do_append = FALSE;
+ }
+ }
+ goto ret;
+ }
+
+ /* file opened, but not fully copied */
+ dst_status = DEST_SHORT_QUERY;
+
+ appending = ctx->do_append;
+ ctx->do_append = FALSE;
+
+ /* Try clone the file first. */
+ if (vfs_clone_file (dest_desc, src_desc) == 0)
+ {
+ dst_status = DEST_FULL;
+ return_status = FILE_CONT;
+ goto ret;
+ }
+
+ /* Find out the optimal buffer size. */
+ while (mc_fstat (dest_desc, &dst_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret;
+ }
+
+ /* try preallocate space; if fail, try copy anyway */
+ while (mc_global.vfs.preallocate_space &&
+ vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
+ {
+ if (ctx->skip_all)
+ {
+ /* cannot allocate, start the file copying anyway */
+ return_status = FILE_CONT;
+ break;
+ }
+
+ return_status =
+ file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
+
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+
+ if (ctx->skip_all || return_status == FILE_SKIP)
+ {
+ /* skip the space allocation error, start file copying */
+ return_status = FILE_CONT;
+ break;
+ }
+
+ if (return_status == FILE_ABORT)
+ {
+ mc_close (dest_desc);
+ dest_desc = -1;
+ mc_unlink (dst_vpath);
+ dst_status = DEST_NONE;
+ goto ret;
+ }
+
+ /* return_status == FILE_RETRY -- try allocate space again */
+ }
+
+ ctx->eta_secs = 0.0;
+ ctx->bps = 0;
+
+ if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL)
+ file_progress_show (ctx, 0, file_size, "", TRUE);
+ else
+ file_progress_show (ctx, 1, 1, "", TRUE);
+ return_status = check_progress_buttons (ctx);
+ mc_refresh ();
+
+ if (return_status == FILE_CONT)
+ {
+ size_t bufsize;
+ off_t file_part = 0;
+ gint64 tv_current, tv_last_update;
+ gint64 tv_last_input = 0;
+ gint64 usecs, update_usecs;
+ const char *stalled_msg = "";
+ gboolean is_first_time = TRUE;
+
+ tv_last_update = tv_transfer_start;
+
+ bufsize = io_blksize (dst_stat);
+ buf = g_malloc (bufsize);
+
+ while (TRUE)
+ {
+ ssize_t n_read = -1, n_written;
+ gboolean force_update;
+
+ /* src_read */
+ if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
+ while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
+ {
+ return_status =
+ file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
+ if (return_status == FILE_RETRY)
+ continue;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ goto ret;
+ }
+
+ if (n_read == 0)
+ break;
+
+ tv_current = g_get_monotonic_time ();
+
+ if (n_read > 0)
+ {
+ char *t = buf;
+
+ file_part += n_read;
+
+ tv_last_input = tv_current;
+
+ /* dst_write */
+ while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
+ {
+ gboolean write_errno_nospace;
+
+ if (n_written > 0)
+ {
+ n_read -= n_written;
+ t += n_written;
+ continue;
+ }
+
+ write_errno_nospace = (n_written < 0 && errno == ENOSPC);
+
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ return_status =
+ file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
+
+ if (return_status == FILE_SKIP)
+ {
+ if (write_errno_nospace)
+ goto ret;
+ break;
+ }
+ if (return_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ if (write_errno_nospace)
+ goto ret;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+ }
+
+ tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget;
+
+ usecs = tv_current - tv_last_update;
+ update_usecs = tv_current - tv_last_input;
+
+ if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
+ {
+ copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start,
+ file_size, file_part);
+ tv_last_update = tv_current;
+ }
+
+ is_first_time = FALSE;
+
+ if (update_usecs > FILEOP_STALLING_INTERVAL_US)
+ stalled_msg = _("(stalled)");
+
+ force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US;
+
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
+ }
+
+ file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
+ force_update);
+ mc_refresh ();
+
+ return_status = check_progress_buttons (ctx);
+ if (return_status != FILE_CONT)
+ {
+ int query_res;
+
+ query_res =
+ query_dialog (Q_ ("DialogTitle|Copy"),
+ _("Incomplete file was retrieved"), D_ERROR, 3,
+ _("&Delete"), _("&Keep"), _("&Continue copy"));
+
+ switch (query_res)
+ {
+ case 0:
+ /* delete */
+ dst_status = DEST_SHORT_DELETE;
+ goto ret;
+
+ case 1:
+ /* keep */
+ dst_status = DEST_SHORT_KEEP;
+ goto ret;
+
+ default:
+ /* continue copy */
+ break;
+ }
+ }
+ }
+
+ /* copy successful */
+ dst_status = DEST_FULL;
+ }
+
+ ret:
+ g_free (buf);
+
+ rotate_dash (FALSE);
+ while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_ABORT)
+ return_status = temp_status;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ break;
+ }
+
+ while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ return_status = temp_status;
+ break;
+ }
+
+ if (dst_status == DEST_SHORT_QUERY)
+ {
+ /* Query to remove short file */
+ if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
+ D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
+ mc_unlink (dst_vpath);
+ }
+ else if (dst_status == DEST_SHORT_DELETE)
+ mc_unlink (dst_vpath);
+ else if (dst_status == DEST_FULL && !appending)
+ {
+ /* Copy has succeeded */
+
+ while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ return_status = FILE_CONT;
+ }
+ if (temp_status == FILE_SKIP)
+ return_status = FILE_CONT;
+ break;
+ }
+
+ while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
+ && !ctx->skip_all)
+ {
+ temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
+ if (temp_status == FILE_RETRY)
+ continue;
+ if (temp_status == FILE_SKIPALL)
+ {
+ ctx->skip_all = TRUE;
+ return_status = FILE_CONT;
+ }
+ if (temp_status == FILE_SKIP)
+ return_status = FILE_CONT;
+ break;
+ }
+
+ if (!ctx->preserve && !dst_exists)
+ {
+ src_mode = umask (-1);
+ umask (src_mode);
+ src_mode = 0100666 & ~src_mode;
+ mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
+ }
+
+ mc_utime (dst_vpath, &times);
+ }
+
+ if (return_status == FILE_CONT)
+ return_status = progress_update_one (tctx, ctx, file_size);
+
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * I think these copy_*_* functions should have a return type.
+ * anyway, this function *must* have two directories as arguments.
+ */
+/* FIXME: This function needs to check the return values of the
+ function calls */
+
+FileProgressStatus
+copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d,
+ gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
+{
+ struct vfs_dirent *next;
+ struct stat dst_stat, src_stat;
+ DIR *reading;
+ FileProgressStatus return_status = FILE_CONT;
+ struct link *lp;
+ vfs_path_t *src_vpath, *dst_vpath;
+ gboolean do_mkdir = TRUE;
+
+ src_vpath = vfs_path_from_str (s);
+ dst_vpath = vfs_path_from_str (d);
+
+ /* First get the mode of the source dir */
+
+ retry_src_stat:
+ if ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
+ if (return_status == FILE_RETRY)
+ goto retry_src_stat;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret_fast;
+ }
+
+ if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
+ {
+ /* Don't copy a directory we created before (we don't want to copy
+ infinitely if a directory is copied into itself) */
+ /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
+ return_status = FILE_CONT;
+ goto ret_fast;
+ }
+
+ /* Hmm, hardlink to directory??? - Norbert */
+ /* FIXME: In this step we should do something in case the destination already exist */
+ /* Check the hardlinks */
+ if (ctx->preserve)
+ {
+ switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
+ {
+ case HARDLINK_OK:
+ /* We have made a hardlink - no more processing is necessary */
+ goto ret_fast;
+
+ case HARDLINK_ABORT:
+ return_status = FILE_ABORT;
+ goto ret_fast;
+
+ default:
+ break;
+ }
+ }
+
+ if (!S_ISDIR (src_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
+ if (return_status == FILE_RETRY)
+ goto retry_src_stat;
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ goto ret_fast;
+ }
+
+ if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
+ {
+ /* we found a cyclic symbolic link */
+ message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
+ return_status = FILE_SKIP;
+ goto ret_fast;
+ }
+
+ lp = g_new0 (struct link, 1);
+ lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
+ lp->ino = src_stat.st_ino;
+ lp->dev = src_stat.st_dev;
+ parent_dirs = g_slist_prepend (parent_dirs, lp);
+
+ retry_dst_stat:
+ /* Now, check if the dest dir exists, if not, create it. */
+ if (mc_stat (dst_vpath, &dst_stat) != 0)
+ {
+ /* Here the dir doesn't exist : make it ! */
+ if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
+ {
+ return_status = FILE_CONT;
+ goto ret;
+ }
+ }
+ else
+ {
+ /*
+ * If the destination directory exists, we want to copy the whole
+ * directory, but we only want this to happen once.
+ *
+ * Escape sequences added to the * to compiler warnings.
+ * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
+ * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
+ */
+ if (!S_ISDIR (dst_stat.st_mode))
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ if (return_status == FILE_RETRY)
+ goto retry_dst_stat;
+ }
+ goto ret;
+ }
+ /* Dive into subdir if exists */
+ if (toplevel && ctx->dive_into_subdirs)
+ {
+ vfs_path_t *tmp;
+
+ tmp = dst_vpath;
+ dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
+ vfs_path_free (tmp, TRUE);
+
+ }
+ else
+ do_mkdir = FALSE;
+ }
+
+ d = vfs_path_as_str (dst_vpath);
+
+ if (do_mkdir)
+ {
+ while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status =
+ file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+
+ lp = g_new0 (struct link, 1);
+ mc_stat (dst_vpath, &dst_stat);
+ lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
+ lp->ino = dst_stat.st_ino;
+ lp->dev = dst_stat.st_dev;
+ dest_dirs = g_slist_prepend (dest_dirs, lp);
+ }
+
+ if (ctx->preserve_uidgid)
+ {
+ while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
+ {
+ if (ctx->skip_all)
+ return_status = FILE_SKIPALL;
+ else
+ {
+ return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
+ if (return_status == FILE_SKIPALL)
+ ctx->skip_all = TRUE;
+ }
+ if (return_status != FILE_RETRY)
+ goto ret;
+ }
+ }
+
+ /* open the source dir for reading */
+ reading = mc_opendir (src_vpath);
+ if (reading == NULL)
+ goto ret;
+
+ while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
+ {
+ char *path;
+ vfs_path_t *tmp_vpath;
+
+ /*
+ * Now, we don't want '.' and '..' to be created / copied at any time
+ */
+ if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
+ continue;
+
+ /* get the filename and add it to the src directory */
+ path = mc_build_filename (s, next->d_name, (char *) NULL);
+ tmp_vpath = vfs_path_from_str (path);
+
+ (*ctx->stat_func) (tmp_vpath, &dst_stat);
+ if (S_ISDIR (dst_stat.st_mode))
+ {
+ char *mdpath;
+
+ mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
+ /*
+ * From here, we just intend to recursively copy subdirs, not
+ * the double functionality of copying different when the target
+ * dir already exists. So, we give the recursive call the flag 0
+ * meaning no toplevel.
+ */
+ return_status =
+ copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
+ g_free (mdpath);
+ }
+ else
+ {
+ char *dest_file;
+
+ dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
+ return_status = copy_file_file (tctx, ctx, path, dest_file);
+ g_free (dest_file);
+ }
+
+ g_free (path);
+
+ if (do_delete && return_status == FILE_CONT)
+ {
+ if (ctx->erase_at_end)
+ {
+ if (erase_list == NULL)
+ erase_list = g_queue_new ();
+
+ lp = g_new0 (struct link, 1);
+ lp->src_vpath = tmp_vpath;
+ lp->st_mode = dst_stat.st_mode;
+ g_queue_push_tail (erase_list, lp);
+ tmp_vpath = NULL;
+ }
+ else if (S_ISDIR (dst_stat.st_mode))
+ return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
+ else
+ return_status = erase_file (tctx, ctx, tmp_vpath);
+ }
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ mc_closedir (reading);
+
+ if (ctx->preserve)
+ {
+ mc_timesbuf_t times;
+
+ mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
+ get_times (&src_stat, &times);
+ mc_utime (dst_vpath, &times);
+ }
+ else
+ {
+ src_stat.st_mode = umask (-1);
+ umask (src_stat.st_mode);
+ src_stat.st_mode = 0100777 & ~src_stat.st_mode;
+ mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
+ }
+
+ ret:
+ free_link (parent_dirs->data);
+ g_slist_free_1 (parent_dirs);
+ ret_fast:
+ vfs_path_free (src_vpath, TRUE);
+ vfs_path_free (dst_vpath, TRUE);
+ return return_status;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Move routines */
+
+FileProgressStatus
+move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d)
+{
+ return do_move_dir_dir (NULL, tctx, ctx, s, d);
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Erase routines */
+
+FileProgressStatus
+erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL);
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ return FILE_ABORT;
+
+ mc_refresh ();
+
+ /* The old way to detect a non empty directory was:
+ error = my_rmdir (s);
+ if (error && (errno == ENOTEMPTY || errno == EEXIST))){
+ For the linux user space nfs server (nfs-server-2.2beta29-2)
+ we would have to check also for EIO. I hope the new way is
+ fool proof. (Norbert)
+ */
+ if (check_dir_is_empty (vpath) == 0)
+ { /* not empty */
+ FileProgressStatus error;
+
+ error = query_recursive (ctx, vfs_path_as_str (vpath));
+ if (error == FILE_CONT)
+ error = recursive_erase (tctx, ctx, vpath);
+ return error;
+ }
+
+ return try_erase_dir (ctx, vfs_path_as_str (vpath));
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Panel operate routines */
+
+void
+dirsize_status_init_cb (status_msg_t * sm)
+{
+ dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
+ WGroup *gd = GROUP (sm->dlg);
+ Widget *wd = WIDGET (sm->dlg);
+ WRect r = wd->rect;
+
+ const char *b1_name = N_("&Abort");
+ const char *b2_name = N_("&Skip");
+ int b_width, ui_width;
+
+#ifdef ENABLE_NLS
+ b1_name = _(b1_name);
+ b2_name = _(b2_name);
+#endif
+
+ b_width = str_term_width1 (b1_name) + 4;
+ if (dsm->allow_skip)
+ b_width += str_term_width1 (b2_name) + 4 + 1;
+
+ ui_width = MAX (COLS / 2, b_width + 6);
+ dsm->dirname = label_new (2, 3, NULL);
+ group_add_widget (gd, dsm->dirname);
+ dsm->count_size = label_new (3, 3, NULL);
+ group_add_widget (gd, dsm->count_size);
+ group_add_widget (gd, hline_new (4, -1, -1));
+
+ dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
+ group_add_widget (gd, dsm->abort_button);
+ if (dsm->allow_skip)
+ {
+ dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
+ group_add_widget (gd, dsm->skip_button);
+ widget_select (dsm->skip_button);
+ }
+
+ r.lines = 8;
+ r.cols = ui_width;
+ widget_set_size_rect (wd, &r);
+ dirsize_status_locate_buttons (dsm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+dirsize_status_update_cb (status_msg_t * sm)
+{
+ dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
+ Widget *wd = WIDGET (sm->dlg);
+ WRect r = wd->rect;
+
+ /* update second (longer label) */
+ label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
+ dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
+
+ /* enlarge dialog if required */
+ if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
+ {
+ r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
+ widget_set_size_rect (wd, &r);
+ dirsize_status_locate_buttons (dsm);
+ widget_draw (wd);
+ /* TODO: ret rid of double redraw */
+ }
+
+ /* adjust first label */
+ label_set_text (dsm->dirname,
+ str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
+
+ switch (status_msg_common_update (sm))
+ {
+ case B_CANCEL:
+ case FILE_ABORT:
+ return FILE_ABORT;
+ case FILE_SKIP:
+ return FILE_SKIP;
+ default:
+ return FILE_CONT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dirsize_status_deinit_cb (status_msg_t * sm)
+{
+ (void) sm;
+
+ /* schedule to update passive panel */
+ if (get_other_type () == view_listing)
+ other_panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * compute_dir_size:
+ *
+ * Computes the number of bytes used by the files in a directory
+ */
+
+FileProgressStatus
+compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
+ size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
+ gboolean follow_symlinks)
+{
+ return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
+ follow_symlinks ? mc_stat : mc_lstat);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_operate:
+ *
+ * Performs one of the operations on the current on the source_panel
+ * (copy, delete, move).
+ *
+ * Returns TRUE if did change the directory
+ * structure, Returns FALSE if user aborted
+ *
+ * force_single forces operation on the current entry and affects
+ * default destination. Current filename is used as default.
+ */
+
+gboolean
+panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
+{
+ WPanel *panel = PANEL (source_panel);
+ const gboolean single_entry = force_single || (panel->marked <= 1)
+ || (get_current_type () == view_tree);
+
+ const char *source = NULL;
+ char *dest = NULL;
+ vfs_path_t *dest_vpath = NULL;
+ vfs_path_t *save_cwd = NULL, *save_dest = NULL;
+ struct stat src_stat;
+ gboolean ret_val = TRUE;
+ int i;
+ FileProgressStatus value;
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+ filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
+
+ gboolean do_bg = FALSE; /* do background operation? */
+
+ static gboolean i18n_flag = FALSE;
+ if (!i18n_flag)
+ {
+ for (i = G_N_ELEMENTS (op_names); i-- != 0;)
+ op_names[i] = Q_ (op_names[i]);
+ i18n_flag = TRUE;
+ }
+
+ linklist = free_linklist (linklist);
+ dest_dirs = free_linklist (dest_dirs);
+
+ save_cwds_stat ();
+
+ if (single_entry)
+ {
+ source = check_single_entry (panel, force_single, &src_stat);
+
+ if (source == NULL)
+ return FALSE;
+ }
+
+ ctx = file_op_context_new (operation);
+
+ /* Show confirmation dialog */
+ if (operation != OP_DELETE)
+ {
+ dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
+ if (dest == NULL)
+ {
+ ret_val = FALSE;
+ goto ret_fast;
+ }
+
+ dest_vpath = vfs_path_from_str (dest);
+ }
+ else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
+ {
+ ret_val = FALSE;
+ goto ret_fast;
+ }
+
+ tctx = file_op_total_context_new ();
+ tctx->transfer_start = g_get_monotonic_time ();
+
+#ifdef ENABLE_BACKGROUND
+ /* Did the user select to do a background operation? */
+ if (do_bg)
+ {
+ int v;
+
+ v = do_background (ctx,
+ g_strconcat (op_names[operation], ": ",
+ vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
+ if (v == -1)
+ message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
+
+ /* If we are the parent */
+ if (v == 1)
+ {
+ mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
+
+ mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
+ vfs_path_free (dest_vpath, TRUE);
+ g_free (dest);
+ /* file_op_context_destroy (ctx); */
+ return FALSE;
+ }
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ {
+ if (operation == OP_DELETE)
+ dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
+ else if (single_entry && S_ISDIR (panel_current_entry (panel)->st.st_mode))
+ dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
+ else if (single_entry || force_single)
+ dialog_type = FILEGUI_DIALOG_ONE_ITEM;
+ else
+ dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
+ }
+
+ /* Initialize things */
+ /* We do not want to trash cache every time file is
+ created/touched. However, this will make our cache contain
+ invalid data. */
+ if ((dest != NULL)
+ && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
+ save_dest = vfs_path_from_str (dest);
+
+ if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
+ && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
+ save_cwd = vfs_path_clone (panel->cwd_vpath);
+
+ /* Now, let's do the job */
+
+ /* This code is only called by the tree and panel code */
+ if (single_entry)
+ {
+ /* We now have ETA in all cases */
+
+ /* One file: FIXME mc_chdir will take user out of any vfs */
+ if ((operation != OP_COPY) && (get_current_type () == view_tree))
+ {
+ vfs_path_t *vpath;
+ int chdir_retcode;
+
+ vpath = vfs_path_from_str (PATH_SEP_STR);
+ chdir_retcode = mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ if (chdir_retcode < 0)
+ {
+ ret_val = FALSE;
+ goto clean_up;
+ }
+ }
+
+ value = operate_single_file (panel, tctx, ctx, source, &src_stat, dest, dialog_type);
+ if ((value == FILE_CONT) && !force_single)
+ unmark_files (panel);
+ }
+ else
+ {
+ /* Many files */
+
+ /* Check destination for copy or move operation */
+ while (operation != OP_DELETE)
+ {
+ int dst_result;
+ struct stat dst_stat;
+
+ dst_result = mc_stat (dest_vpath, &dst_stat);
+
+ if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
+ break;
+
+ if (ctx->skip_all
+ || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
+ dest) != FILE_RETRY)
+ goto clean_up;
+ }
+
+ /* TODO: the good way is required to skip directories scanning in case of rename/move
+ * of several directories. Since reqular expression can be used for destination,
+ * some directory movements can be a cross-filesystem and directory scanning is useful
+ * for those directories only. */
+
+ if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
+ == FILE_CONT)
+ {
+ /* Loop for every file, perform the actual copy operation */
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ const char *source2;
+
+ if (panel->dir.list[i].f.marked == 0)
+ continue; /* Skip the unmarked ones */
+
+ source2 = panel->dir.list[i].fname->str;
+ src_stat = panel->dir.list[i].st;
+
+ value = operate_one_file (panel, tctx, ctx, source2, &src_stat, dest);
+
+ if (value == FILE_ABORT)
+ break;
+
+ if (value == FILE_CONT)
+ do_file_mark (panel, i, 0);
+
+ if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
+ file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
+ }
+
+ if (operation != OP_DELETE)
+ file_progress_show (ctx, 0, 0, "", FALSE);
+
+ if (check_progress_buttons (ctx) == FILE_ABORT)
+ break;
+
+ mc_refresh ();
+ } /* Loop for every file */
+ }
+ } /* Many entries */
+
+ clean_up:
+ /* Clean up */
+ if (save_cwd != NULL)
+ {
+ mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
+ vfs_path_free (save_cwd, TRUE);
+ }
+
+ if (save_dest != NULL)
+ {
+ mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
+ vfs_path_free (save_dest, TRUE);
+ }
+
+ linklist = free_linklist (linklist);
+ dest_dirs = free_linklist (dest_dirs);
+ g_free (dest);
+ vfs_path_free (dest_vpath, TRUE);
+ MC_PTR_FREE (ctx->dest_mask);
+
+#ifdef ENABLE_BACKGROUND
+ /* Let our parent know we are saying bye bye */
+ if (mc_global.we_are_background)
+ {
+ int cur_pid = getpid ();
+ /* Send pid to parent with child context, it is fork and
+ don't modify real parent ctx */
+ ctx->pid = cur_pid;
+ parent_call ((void *) end_bg_process, ctx, 0);
+
+ vfs_shut ();
+ my_exit (EXIT_SUCCESS);
+ }
+#endif /* ENABLE_BACKGROUND */
+
+ file_op_total_context_destroy (tctx);
+ ret_fast:
+ file_op_context_destroy (ctx);
+
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+ repaint_screen ();
+
+ return ret_val;
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ Query/status report routines */
+/** Report error with one file */
+FileProgressStatus
+file_error (gboolean allow_retry, const char *format, const char *file)
+{
+ char buf[BUF_MEDIUM];
+
+ g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
+
+ return do_file_error (allow_retry, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/*
+ Cause emacs to enter folding mode for this file:
+ Local variables:
+ end:
+ */
diff --git a/src/filemanager/file.h b/src/filemanager/file.h
new file mode 100644
index 0000000..ae12e15
--- /dev/null
+++ b/src/filemanager/file.h
@@ -0,0 +1,72 @@
+/** \file file.h
+ * \brief Header: File and directory operation routines
+ */
+
+#ifndef MC__FILE_H
+#define MC__FILE_H
+
+#include <inttypes.h> /* off_t, uintmax_t */
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "fileopctx.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef struct dirsize_status_msg_t dirsize_status_msg_t;
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* status dialog of directory size computing */
+struct dirsize_status_msg_t
+{
+ status_msg_t status_msg; /* base class */
+
+ gboolean allow_skip;
+ WLabel *dirname;
+ WLabel *count_size;
+ Widget *abort_button;
+ Widget *skip_button;
+ const vfs_path_t *dirname_vpath;
+ size_t dir_count;
+ uintmax_t total_size;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+gboolean file_is_symlink_to_dir (const vfs_path_t * path, struct stat *st, gboolean * stale_link);
+
+FileProgressStatus copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *src_path, const char *dst_path);
+FileProgressStatus move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d);
+FileProgressStatus copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const char *s, const char *d,
+ gboolean toplevel, gboolean move_over, gboolean do_delete,
+ GSList * parent_dirs);
+FileProgressStatus erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ const vfs_path_t * vpath);
+
+gboolean panel_operate (void *source_panel, FileOperation op, gboolean force_single);
+
+/* Error reporting routines */
+
+/* Report error with one file */
+FileProgressStatus file_error (gboolean allow_retry, const char *format, const char *file);
+
+/* return value is FILE_CONT or FILE_ABORT */
+FileProgressStatus compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
+ size_t * ret_dir_count, size_t * ret_marked_count,
+ uintmax_t * ret_total, gboolean follow_symlinks);
+
+void dirsize_status_init_cb (status_msg_t * sm);
+int dirsize_status_update_cb (status_msg_t * sm);
+void dirsize_status_deinit_cb (status_msg_t * sm);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILE_H */
diff --git a/src/filemanager/filegui.c b/src/filemanager/filegui.c
new file mode 100644
index 0000000..abca598
--- /dev/null
+++ b/src/filemanager/filegui.c
@@ -0,0 +1,1498 @@
+/*
+ File management GUI for the text mode edition
+
+ The copy code was based in GNU's cp, and was written by:
+ Torbjorn Granlund, David MacKenzie, and Jim Meyering.
+
+ The move code was based in GNU's mv, and was written by:
+ Mike Parker and David MacKenzie.
+
+ Janne Kukonlehto added much error recovery to them for being used
+ in an interactive program.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1995
+ Fred Leeflang, 1994, 1995
+ Miguel de Icaza, 1994, 1995, 1996
+ Jakub Jelinek, 1995, 1996
+ Norbert Warmuth, 1997
+ Pavel Machek, 1998
+ Slava Zanko, 2009, 2010, 2011, 2012, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Please note that all dialogs used here must be safe for background
+ * operations.
+ */
+
+/** \file filegui.c
+ * \brief Source: file management GUI for the text mode edition
+ */
+
+/* {{{ Include files */
+
+#include <config.h>
+
+#if ((defined STAT_STATVFS || defined STAT_STATVFS64) \
+ && (defined HAVE_STRUCT_STATVFS_F_BASETYPE || defined HAVE_STRUCT_STATVFS_F_FSTYPENAME \
+ || (! defined HAVE_STRUCT_STATFS_F_FSTYPENAME)))
+#define USE_STATVFS 1
+#else
+#define USE_STATVFS 0
+#endif
+
+#include <errno.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if USE_STATVFS
+#include <sys/statvfs.h>
+#elif defined HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#elif defined HAVE_SYS_MOUNT_H && defined HAVE_SYS_PARAM_H
+/* NOTE: freebsd5.0 needs sys/param.h and sys/mount.h for statfs.
+ It does have statvfs.h, but shouldn't use it, since it doesn't
+ HAVE_STRUCT_STATVFS_F_BASETYPE. So find a clean way to fix it. */
+/* NetBSD 1.5.2 needs these, for the declaration of struct statfs. */
+#include <sys/param.h>
+#include <sys/mount.h>
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#include <fs_info.h>
+#endif
+
+#if USE_STATVFS
+#if ! defined STAT_STATVFS && defined STAT_STATVFS64
+#define STRUCT_STATVFS struct statvfs64
+#define STATFS statvfs64
+#else
+#define STRUCT_STATVFS struct statvfs
+#define STATFS statvfs
+
+#if defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__)
+#include <sys/utsname.h>
+#include <sys/statfs.h>
+#define STAT_STATFS2_BSIZE 1
+#endif
+#endif
+
+#else
+#define STATFS statfs
+#define STRUCT_STATVFS struct statfs
+#ifdef HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+/* BeOS has a statvfs function, but it does not return sensible values
+ for f_files, f_ffree and f_favail, and lacks f_type, f_basetype and
+ f_fstypename. Use 'struct fs_info' instead. */
+static int
+statfs (char const *filename, struct fs_info *buf)
+{
+ dev_t device;
+
+ device = dev_for_path (filename);
+
+ if (device < 0)
+ {
+ errno = (device == B_ENTRY_NOT_FOUND ? ENOENT
+ : device == B_BAD_VALUE ? EINVAL
+ : device == B_NAME_TOO_LONG ? ENAMETOOLONG
+ : device == B_NO_MEMORY ? ENOMEM : device == B_FILE_ERROR ? EIO : 0);
+ return -1;
+ }
+ /* If successful, buf->dev will be == device. */
+ return fs_stat_dev (device, buf);
+}
+
+#define STRUCT_STATVFS struct fs_info
+#else
+#define STRUCT_STATVFS struct statfs
+#endif
+#endif
+
+#ifdef HAVE_STRUCT_STATVFS_F_BASETYPE
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_basetype
+#else
+#if defined HAVE_STRUCT_STATVFS_F_FSTYPENAME || defined HAVE_STRUCT_STATFS_F_FSTYPENAME
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME f_fstypename
+#elif defined HAVE_OS_H /* Haiku, also (obsolete) BeOS */
+#define STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME fsh_name
+#endif
+#endif
+
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/key.h" /* tty_get_event */
+#include "lib/mcconfig.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* verbose, safe_overwrite */
+
+#include "filemanager.h"
+#include "fileopctx.h" /* FILE_CONT */
+
+#include "filegui.h"
+
+/* }}} */
+
+/*** global variables ****************************************************************************/
+
+gboolean classic_progressbar = TRUE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define truncFileString(dlg, s) str_trunc (s, WIDGET (dlg)->rect.cols - 10)
+#define truncFileStringSecure(dlg, s) path_trunc (s, WIDGET (dlg)->rect.cols - 10)
+
+/*** file scope type declarations ****************************************************************/
+
+/* *INDENT-OFF* */
+typedef enum {
+ MSDOS_SUPER_MAGIC = 0x4d44,
+ NTFS_SB_MAGIC = 0x5346544e,
+ FUSE_MAGIC = 0x65735546,
+ PROC_SUPER_MAGIC = 0x9fa0,
+ SMB_SUPER_MAGIC = 0x517B,
+ NCP_SUPER_MAGIC = 0x564c,
+ USBDEVICE_SUPER_MAGIC = 0x9fa2
+} filegui_nonattrs_fs_t;
+/* *INDENT-ON* */
+
+/* Used for button result values */
+typedef enum
+{
+ REPLACE_YES = B_USER,
+ REPLACE_NO,
+ REPLACE_APPEND,
+ REPLACE_REGET,
+ REPLACE_ALL,
+ REPLACE_OLDER,
+ REPLACE_NONE,
+ REPLACE_SMALLER,
+ REPLACE_SIZE,
+ REPLACE_ABORT
+} replace_action_t;
+
+/* This structure describes the UI and internal data required by a file
+ * operation context.
+ */
+typedef struct
+{
+ /* ETA and bps */
+ gboolean showing_eta;
+ gboolean showing_bps;
+
+ /* Dialog and widgets for the operation progress window */
+ WDialog *op_dlg;
+ /* Source file: label and name */
+ WLabel *src_file_label;
+ WLabel *src_file;
+ /* Target file: label and name */
+ WLabel *tgt_file_label;
+ WLabel *tgt_file;
+
+ WGauge *progress_file_gauge;
+ WLabel *progress_file_label;
+
+ WGauge *progress_total_gauge;
+
+ WLabel *total_files_processed_label;
+ WLabel *time_label;
+ WHLine *total_bytes_label;
+
+ /* Query replace dialog */
+ WDialog *replace_dlg;
+ const char *src_filename;
+ const char *tgt_filename;
+ replace_action_t replace_result;
+ gboolean dont_overwrite_with_zero;
+
+ struct stat *src_stat, *dst_stat;
+} file_op_context_ui_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ Widget *w;
+ FileProgressStatus action;
+ const char *text;
+ button_flags_t flags;
+ int len;
+} progress_buttons[] =
+{
+ /* *INDENT-OFF* */
+ { NULL, FILE_SKIP, N_("&Skip"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("S&uspend"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_SUSPEND, N_("Con&tinue"), NORMAL_BUTTON, -1 },
+ { NULL, FILE_ABORT, N_("&Abort"), NORMAL_BUTTON, -1 }
+ /* *INDENT-ON* */
+};
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return true if statvfs works. This is false for statvfs on systems
+ with GNU libc on Linux kernels before 2.6.36, which stats all
+ preceding entries in /proc/mounts; that makes df hang if even one
+ of the corresponding file systems is hard-mounted but not available. */
+
+#if USE_STATVFS && ! (! defined STAT_STATVFS && defined STAT_STATVFS64)
+static int
+statvfs_works (void)
+{
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+ return 1;
+#else
+ static int statvfs_works_cache = -1;
+ struct utsname name;
+
+ if (statvfs_works_cache < 0)
+ statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
+ return statvfs_works_cache;
+#endif
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+filegui__check_attrs_on_fs (const char *fs_path)
+{
+ STRUCT_STATVFS stfs;
+
+#if USE_STATVFS && defined(STAT_STATVFS)
+ if (statvfs_works () && statvfs (fs_path, &stfs) != 0)
+ return TRUE;
+#else
+ if (STATFS (fs_path, &stfs) != 0)
+ return TRUE;
+#endif
+
+#if (USE_STATVFS && defined(HAVE_STRUCT_STATVFS_F_TYPE)) || \
+ (!USE_STATVFS && defined(HAVE_STRUCT_STATFS_F_TYPE))
+ switch ((filegui_nonattrs_fs_t) stfs.f_type)
+ {
+ case MSDOS_SUPER_MAGIC:
+ case NTFS_SB_MAGIC:
+ case PROC_SUPER_MAGIC:
+ case SMB_SUPER_MAGIC:
+ case NCP_SUPER_MAGIC:
+ case USBDEVICE_SUPER_MAGIC:
+ return FALSE;
+ default:
+ break;
+ }
+#elif defined(HAVE_STRUCT_STATVFS_F_FSTYPENAME) || defined(HAVE_STRUCT_STATFS_F_FSTYPENAME)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdos") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "msdosfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "procfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strstr (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fusefs") != NULL)
+ return FALSE;
+#elif defined(HAVE_STRUCT_STATVFS_F_BASETYPE)
+ if (strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "pcfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "ntfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "proc") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "smbfs") == 0
+ || strcmp (stfs.STATXFS_FILE_SYSTEM_TYPE_MEMBER_NAME, "fuse") == 0)
+ return FALSE;
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_frmt_time (char *buffer, double eta_secs)
+{
+ int eta_hours, eta_mins, eta_s;
+
+ eta_hours = (int) (eta_secs / (60 * 60));
+ eta_mins = (int) ((eta_secs - (eta_hours * 60 * 60)) / 60);
+ eta_s = (int) (eta_secs - (eta_hours * 60 * 60 + eta_mins * 60));
+ g_snprintf (buffer, BUF_TINY, _("%d:%02d:%02d"), eta_hours, eta_mins, eta_s);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_eta_prepare_for_show (char *buffer, double eta_secs, gboolean always_show)
+{
+ char _fmt_buff[BUF_TINY];
+
+ if (eta_secs <= 0.5 && !always_show)
+ {
+ *buffer = '\0';
+ return;
+ }
+
+ if (eta_secs <= 0.5)
+ eta_secs = 1;
+ file_frmt_time (_fmt_buff, eta_secs);
+ g_snprintf (buffer, BUF_TINY, _("ETA %s"), _fmt_buff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+file_bps_prepare_for_show (char *buffer, long bps)
+{
+ if (bps > 1024 * 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f MB/s"), bps / (1024 * 1024.0));
+ else if (bps > 1024)
+ g_snprintf (buffer, BUF_TINY, _("%.2f KB/s"), bps / 1024.0);
+ else if (bps > 1)
+ g_snprintf (buffer, BUF_TINY, _("%ld B/s"), bps);
+ else
+ *buffer = '\0';
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+file_ui_op_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_ACTION:
+ /* Do not close the dialog because the query dialog will be shown */
+ if (parm == CK_Cancel)
+ {
+ DIALOG (w)->ret_value = FILE_ABORT; /* for check_progress_buttons() */
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* The dialog layout:
+ *
+ * +---------------------- File exists -----------------------+
+ * | New : /path/to/original_file_name | // 0, 1
+ * | 1234567 feb 4 2017 13:38 | // 2, 3
+ * | Existing: /path/to/target_file_name | // 4, 5
+ * | 1234567890 feb 4 2017 13:37 | // 6, 7
+ * +----------------------------------------------------------+
+ * | Overwrite this file? | // 8
+ * | [ Yes ] [ No ] [ Append ] [ Reget ] | // 9, 10, 11, 12
+ * +----------------------------------------------------------+
+ * | Overwrite all files? | // 13
+ * | [ ] Don't overwrite with zero length file | // 14
+ * | [ All ] [ Older ] [None] [ Smaller ] [ Size differs ] | // 15, 16, 17, 18, 19
+ * +----------------------------------------------------------|
+ * | [ Abort ] | // 20
+ * +----------------------------------------------------------+
+ */
+
+static replace_action_t
+overwrite_query_dialog (file_op_context_t * ctx, enum OperationMode mode)
+{
+#define W(i) dlg_widgets[i].widget
+#define WX(i) W(i)->rect.x
+#define WY(i) W(i)->rect.y
+#define WCOLS(i) W(i)->rect.cols
+
+#define NEW_LABEL(i, text) \
+ W(i) = WIDGET (label_new (dlg_widgets[i].y, dlg_widgets[i].x, text))
+
+#define ADD_LABEL(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, \
+ g->current != NULL ? g->current->data : NULL)
+
+#define NEW_BUTTON(i) \
+ W(i) = WIDGET (button_new (dlg_widgets[i].y, dlg_widgets[i].x, \
+ dlg_widgets[i].value, NORMAL_BUTTON, dlg_widgets[i].text, NULL))
+
+#define ADD_BUTTON(i) \
+ group_add_widget_autopos (g, W(i), dlg_widgets[i].pos_flags, g->current->data)
+
+ /* dialog sizes */
+ const int dlg_height = 17;
+ int dlg_width = 60;
+
+ struct
+ {
+ Widget *widget;
+ const char *text;
+ int y;
+ int x;
+ widget_pos_flags_t pos_flags;
+ int value; /* 0 for labels and checkbox */
+ } dlg_widgets[] =
+ {
+ /* *INDENT-OFF* */
+ /* 0 - label */
+ { NULL, N_("New :"), 2, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 1 - label - name */
+ { NULL, NULL, 2, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 2 - label - size */
+ { NULL, NULL, 3, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 3 - label - date & time */
+ { NULL, NULL, 3, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* 4 - label */
+ { NULL, N_("Existing:"), 4, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 5 - label - name */
+ { NULL, NULL, 4, 14, WPOS_KEEP_DEFAULT, 0 },
+ /* 6 - label - size */
+ { NULL, NULL, 5, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 7 - label - date & time */
+ { NULL, NULL, 5, 43, WPOS_KEEP_TOP | WPOS_KEEP_RIGHT, 0 },
+ /* --------------------------------------------------- */
+ /* 8 - label */
+ { NULL, N_("Overwrite this file?"), 7, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 9 - button */
+ { NULL, N_("&Yes"), 8, 14, WPOS_KEEP_DEFAULT, REPLACE_YES },
+ /* 10 - button */
+ { NULL, N_("&No"), 8, 22, WPOS_KEEP_DEFAULT, REPLACE_NO },
+ /* 11 - button */
+ { NULL, N_("A&ppend"), 8, 29, WPOS_KEEP_DEFAULT, REPLACE_APPEND },
+ /* 12 - button */
+ { NULL, N_("&Reget"), 8, 40, WPOS_KEEP_DEFAULT, REPLACE_REGET },
+ /* --------------------------------------------------- */
+ /* 13 - label */
+ { NULL, N_("Overwrite all files?"), 10, 21, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, 0 },
+ /* 14 - checkbox */
+ { NULL, N_("Don't overwrite with &zero length file"), 11, 3, WPOS_KEEP_DEFAULT, 0 },
+ /* 15 - button */
+ { NULL, N_("A&ll"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_ALL },
+ /* 16 - button */
+ { NULL, N_("&Older"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_OLDER },
+ /* 17 - button */
+ { NULL, N_("Non&e"), 12, 12, WPOS_KEEP_DEFAULT, REPLACE_NONE },
+ /* 18 - button */
+ { NULL, N_("S&maller"), 12, 25, WPOS_KEEP_DEFAULT, REPLACE_SMALLER },
+ /* 19 - button */
+ { NULL, N_("&Size differs"), 12, 40, WPOS_KEEP_DEFAULT, REPLACE_SIZE },
+ /* --------------------------------------------------- */
+ /* 20 - button */
+ { NULL, N_("&Abort"), 14, 27, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, REPLACE_ABORT }
+ /* *INDENT-ON* */
+ };
+
+ const int gap = 1;
+
+ file_op_context_ui_t *ui = ctx->ui;
+ Widget *wd;
+ WGroup *g;
+ const char *title;
+
+ vfs_path_t *p;
+ char *s1;
+ const char *cs1;
+ char s2[BUF_SMALL];
+ int w, bw1, bw2;
+ unsigned short i;
+
+ gboolean do_append = FALSE, do_reget = FALSE;
+ unsigned long yes_id, no_id;
+ int result;
+
+ if (mode == Foreground)
+ title = _("File exists");
+ else
+ title = _("Background process: File exists");
+
+#ifdef ENABLE_NLS
+ {
+ const unsigned short num = G_N_ELEMENTS (dlg_widgets);
+
+ for (i = 0; i < num; i++)
+ if (dlg_widgets[i].text != NULL)
+ dlg_widgets[i].text = _(dlg_widgets[i].text);
+ }
+#endif /* ENABLE_NLS */
+
+ /* create widgets to get their real widths */
+ /* new file */
+ NEW_LABEL (0, dlg_widgets[0].text);
+ /* new file name */
+ p = vfs_path_from_str (ui->src_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (1, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* new file size */
+ size_trunc_len (s2, sizeof (s2), ui->src_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (2, s2);
+ /* new file modification date & time */
+ cs1 = file_date (ui->src_stat->st_mtime);
+ NEW_LABEL (3, cs1);
+
+ /* existing file */
+ NEW_LABEL (4, dlg_widgets[4].text);
+ /* existing file name */
+ p = vfs_path_from_str (ui->tgt_filename);
+ s1 = vfs_path_to_str_flags (p, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+ NEW_LABEL (5, s1);
+ vfs_path_free (p, TRUE);
+ g_free (s1);
+ /* existing file size */
+ size_trunc_len (s2, sizeof (s2), ui->dst_stat->st_size, 0, panels_options.kilobyte_si);
+ NEW_LABEL (6, s2);
+ /* existing file modification date & time */
+ cs1 = file_date (ui->dst_stat->st_mtime);
+ NEW_LABEL (7, cs1);
+
+ /* will "Append" and "Reget" buttons be in the dialog? */
+ do_append = !S_ISDIR (ui->dst_stat->st_mode);
+ do_reget = do_append && ctx->operation == OP_COPY && ui->dst_stat->st_size != 0
+ && ui->src_stat->st_size > ui->dst_stat->st_size;
+
+ NEW_LABEL (8, dlg_widgets[8].text);
+ NEW_BUTTON (9);
+ NEW_BUTTON (10);
+ if (do_append)
+ NEW_BUTTON (11);
+ if (do_reget)
+ NEW_BUTTON (12);
+
+ NEW_LABEL (13, dlg_widgets[13].text);
+ dlg_widgets[14].widget =
+ WIDGET (check_new (dlg_widgets[14].y, dlg_widgets[14].x, FALSE, dlg_widgets[14].text));
+ for (i = 15; i <= 20; i++)
+ NEW_BUTTON (i);
+
+ /* place widgets */
+ dlg_width -= 2 * (2 + gap); /* inside frame */
+
+ /* perhaps longest line is buttons */
+ bw1 = WCOLS (9) + gap + WCOLS (10);
+ if (do_append)
+ bw1 += gap + WCOLS (11);
+ if (do_reget)
+ bw1 += gap + WCOLS (12);
+ dlg_width = MAX (dlg_width, bw1);
+
+ bw2 = WCOLS (15);
+ for (i = 16; i <= 19; i++)
+ bw2 += gap + WCOLS (i);
+ dlg_width = MAX (dlg_width, bw2);
+
+ dlg_width = MAX (dlg_width, WCOLS (8));
+ dlg_width = MAX (dlg_width, WCOLS (13));
+ dlg_width = MAX (dlg_width, WCOLS (14));
+
+ /* truncate file names */
+ w = WCOLS (0) + gap + WCOLS (1);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (1));
+
+ w = dlg_width - gap - WCOLS (0);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ w = WCOLS (4) + gap + WCOLS (5);
+ if (w > dlg_width)
+ {
+ WLabel *l = LABEL (W (5));
+
+ w = dlg_width - gap - WCOLS (4);
+ label_set_text (l, str_trunc (l->text, w));
+ }
+
+ /* real dlalog width */
+ dlg_width += 2 * (2 + gap);
+
+ WX (1) = WX (0) + WCOLS (0) + gap;
+ WX (5) = WX (4) + WCOLS (4) + gap;
+
+ /* sizes: right alignment */
+ WX (2) = dlg_width / 2 - WCOLS (2);
+ WX (6) = dlg_width / 2 - WCOLS (6);
+
+ w = dlg_width - (2 + gap); /* right bound */
+
+ /* date & time */
+ WX (3) = w - WCOLS (3);
+ WX (7) = w - WCOLS (7);
+
+ /* buttons: center alignment */
+ WX (9) = dlg_width / 2 - bw1 / 2;
+ WX (10) = WX (9) + WCOLS (9) + gap;
+ if (do_append)
+ WX (11) = WX (10) + WCOLS (10) + gap;
+ if (do_reget)
+ WX (12) = WX (11) + WCOLS (11) + gap;
+
+ WX (15) = dlg_width / 2 - bw2 / 2;
+ for (i = 16; i <= 19; i++)
+ WX (i) = WX (i - 1) + WCOLS (i - 1) + gap;
+
+ /* TODO: write help (ticket #3970) */
+ ui->replace_dlg =
+ dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, alarm_colors, NULL, NULL,
+ "[Replace]", title);
+ wd = WIDGET (ui->replace_dlg);
+ g = GROUP (ui->replace_dlg);
+
+ /* file info */
+ for (i = 0; i <= 7; i++)
+ ADD_LABEL (i);
+ group_add_widget (g, hline_new (WY (7) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (8); /* Overwrite this file? */
+ yes_id = ADD_BUTTON (9); /* Yes */
+ no_id = ADD_BUTTON (10); /* No */
+ if (do_append)
+ ADD_BUTTON (11); /* Append */
+ if (do_reget)
+ ADD_BUTTON (12); /* Reget */
+ group_add_widget (g, hline_new (WY (10) - wd->rect.y + 1, -1, -1));
+
+ /* label & buttons */
+ ADD_LABEL (13); /* Overwrite all files? */
+ group_add_widget (g, dlg_widgets[14].widget);
+ for (i = 15; i <= 19; i++)
+ ADD_BUTTON (i);
+ group_add_widget (g, hline_new (WY (19) - wd->rect.y + 1, -1, -1));
+
+ ADD_BUTTON (20); /* Abort */
+
+ group_select_widget_by_id (g, safe_overwrite ? no_id : yes_id);
+
+ result = dlg_run (ui->replace_dlg);
+
+ if (result != B_CANCEL)
+ ui->dont_overwrite_with_zero = CHECK (dlg_widgets[14].widget)->state;
+
+ widget_destroy (wd);
+
+ return (result == B_CANCEL) ? REPLACE_ABORT : (replace_action_t) result;
+
+#undef ADD_BUTTON
+#undef NEW_BUTTON
+#undef ADD_LABEL
+#undef NEW_LABEL
+#undef WCOLS
+#undef WX
+#undef W
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_wildcarded (const char *p)
+{
+ gboolean escaped = FALSE;
+
+ for (; *p != '\0'; p++)
+ {
+ if (*p == '\\')
+ {
+ if (p[1] >= '1' && p[1] <= '9' && !escaped)
+ return TRUE;
+ escaped = !escaped;
+ }
+ else
+ {
+ if ((*p == '*' || *p == '?') && !escaped)
+ return TRUE;
+ escaped = FALSE;
+ }
+ }
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+place_progress_buttons (WDialog * h, gboolean suspended)
+{
+ const size_t i = suspended ? 2 : 1;
+ Widget *w = WIDGET (h);
+ int buttons_width;
+
+ buttons_width = 2 + progress_buttons[0].len + progress_buttons[3].len;
+ buttons_width += progress_buttons[i].len;
+ button_set_text (BUTTON (progress_buttons[i].w), progress_buttons[i].text);
+
+ progress_buttons[0].w->rect.x = w->rect.x + (w->rect.cols - buttons_width) / 2;
+ progress_buttons[i].w->rect.x = progress_buttons[0].w->rect.x + progress_buttons[0].len + 1;
+ progress_buttons[3].w->rect.x = progress_buttons[i].w->rect.x + progress_buttons[i].len + 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+progress_button_callback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ /* don't close dialog in any case */
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+check_progress_buttons (file_op_context_t * ctx)
+{
+ int c;
+ Gpm_Event event;
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ get_event:
+ event.x = -1; /* Don't show the GPM cursor */
+ c = tty_get_event (&event, FALSE, ctx->suspended);
+ if (c == EV_NONE)
+ return FILE_CONT;
+
+ /* Reinitialize to avoid old values after events other than selecting a button */
+ ui->op_dlg->ret_value = FILE_CONT;
+
+ dlg_process_event (ui->op_dlg, c, &event);
+ switch (ui->op_dlg->ret_value)
+ {
+ case FILE_SKIP:
+ if (ctx->suspended)
+ {
+ /* redraw dialog in case of Skip after Suspend */
+ place_progress_buttons (ui->op_dlg, FALSE);
+ widget_draw (WIDGET (ui->op_dlg));
+ }
+ ctx->suspended = FALSE;
+ return FILE_SKIP;
+ case B_CANCEL:
+ case FILE_ABORT:
+ ctx->suspended = FALSE;
+ return FILE_ABORT;
+ case FILE_SUSPEND:
+ ctx->suspended = !ctx->suspended;
+ place_progress_buttons (ui->op_dlg, ctx->suspended);
+ widget_draw (WIDGET (ui->op_dlg));
+ MC_FALLTHROUGH;
+ default:
+ if (ctx->suspended)
+ goto get_event;
+ return FILE_CONT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* {{{ File progress display routines */
+
+void
+file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
+ filegui_dialog_type_t dialog_type)
+{
+ file_op_context_ui_t *ui;
+ Widget *w;
+ WGroup *g;
+ int buttons_width;
+ int dlg_width = 58, dlg_height = 17;
+ int y = 2, x = 3;
+ WRect r;
+
+ if (ctx == NULL || ctx->ui != NULL)
+ return;
+
+#ifdef ENABLE_NLS
+ if (progress_buttons[0].len == -1)
+ {
+ size_t i;
+
+ for (i = 0; i < G_N_ELEMENTS (progress_buttons); i++)
+ progress_buttons[i].text = _(progress_buttons[i].text);
+ }
+#endif
+
+ ctx->dialog_type = dialog_type;
+ ctx->recursive_result = RECURSIVE_YES;
+ ctx->ui = g_new0 (file_op_context_ui_t, 1);
+
+ ui = ctx->ui;
+ ui->replace_result = REPLACE_YES;
+
+ ui->op_dlg = dlg_create (TRUE, 0, 0, dlg_height, dlg_width, WPOS_CENTER, FALSE, dialog_colors,
+ file_ui_op_dlg_callback, NULL, NULL, op_names[ctx->operation]);
+ w = WIDGET (ui->op_dlg);
+ g = GROUP (ui->op_dlg);
+
+ if (dialog_type != FILEGUI_DIALOG_DELETE_ITEM)
+ {
+ ui->showing_eta = with_eta && ctx->progress_totals_computed;
+ ui->showing_bps = with_eta;
+
+ ui->src_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file_label);
+
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->tgt_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file_label);
+
+ ui->tgt_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->tgt_file);
+
+ ui->progress_file_gauge = gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_file_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_file_gauge, WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+
+ ui->progress_file_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->progress_file_label);
+
+ if (verbose && dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
+ {
+ ui->total_bytes_label = hline_new (y++, -1, -1);
+ group_add_widget (g, ui->total_bytes_label);
+
+ if (ctx->progress_totals_computed)
+ {
+ ui->progress_total_gauge =
+ gauge_new (y++, x + 3, dlg_width - (x + 3) * 2, FALSE, 100, 0);
+ if (!classic_progressbar && (current_panel == right_panel))
+ ui->progress_total_gauge->from_left_to_right = FALSE;
+ group_add_widget_autopos (g, ui->progress_total_gauge,
+ WPOS_KEEP_TOP | WPOS_KEEP_HORZ, NULL);
+ }
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+
+ ui->time_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->time_label);
+ }
+ }
+ else
+ {
+ ui->src_file = label_new (y++, x, NULL);
+ group_add_widget (g, ui->src_file);
+
+ ui->total_files_processed_label = label_new (y++, x, NULL);
+ group_add_widget (g, ui->total_files_processed_label);
+ }
+
+ group_add_widget (g, hline_new (y++, -1, -1));
+
+ progress_buttons[0].w = WIDGET (button_new (y, 0, progress_buttons[0].action,
+ progress_buttons[0].flags, progress_buttons[0].text,
+ progress_button_callback));
+ if (progress_buttons[0].len == -1)
+ progress_buttons[0].len = button_get_len (BUTTON (progress_buttons[0].w));
+
+ progress_buttons[1].w = WIDGET (button_new (y, 0, progress_buttons[1].action,
+ progress_buttons[1].flags, progress_buttons[1].text,
+ progress_button_callback));
+ if (progress_buttons[1].len == -1)
+ progress_buttons[1].len = button_get_len (BUTTON (progress_buttons[1].w));
+
+ if (progress_buttons[2].len == -1)
+ {
+ /* create and destroy button to get it length */
+ progress_buttons[2].w = WIDGET (button_new (y, 0, progress_buttons[2].action,
+ progress_buttons[2].flags,
+ progress_buttons[2].text,
+ progress_button_callback));
+ progress_buttons[2].len = button_get_len (BUTTON (progress_buttons[2].w));
+ widget_destroy (progress_buttons[2].w);
+ }
+ progress_buttons[2].w = progress_buttons[1].w;
+
+ progress_buttons[3].w = WIDGET (button_new (y, 0, progress_buttons[3].action,
+ progress_buttons[3].flags, progress_buttons[3].text,
+ NULL));
+ if (progress_buttons[3].len == -1)
+ progress_buttons[3].len = button_get_len (BUTTON (progress_buttons[3].w));
+
+ group_add_widget (g, progress_buttons[0].w);
+ group_add_widget (g, progress_buttons[1].w);
+ group_add_widget (g, progress_buttons[3].w);
+
+ buttons_width = 2 +
+ progress_buttons[0].len + MAX (progress_buttons[1].len, progress_buttons[2].len) +
+ progress_buttons[3].len;
+
+ /* adjust dialog sizes */
+ r = w->rect;
+ r.lines = y + 3;
+ r.cols = MAX (COLS * 2 / 3, buttons_width + 6);
+ widget_set_size_rect (w, &r);
+
+ place_progress_buttons (ui->op_dlg, FALSE);
+
+ widget_select (progress_buttons[0].w);
+
+ /* We will manage the dialog without any help, that's why
+ we have to call dlg_init */
+ dlg_init (ui->op_dlg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_op_context_destroy_ui (file_op_context_t * ctx)
+{
+ if (ctx != NULL && ctx->ui != NULL)
+ {
+ file_op_context_ui_t *ui = (file_op_context_ui_t *) ctx->ui;
+
+ dlg_run_done (ui->op_dlg);
+ widget_destroy (WIDGET (ui->op_dlg));
+ MC_PTR_FREE (ctx->ui);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ show progressbar for file
+ */
+
+void
+file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
+ const char *stalled_msg, gboolean force_update)
+{
+ file_op_context_ui_t *ui;
+
+ if (!verbose || ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (total == 0)
+ {
+ gauge_show (ui->progress_file_gauge, FALSE);
+ return;
+ }
+
+ gauge_set_value (ui->progress_file_gauge, 1024, (int) (1024 * done / total));
+ gauge_show (ui->progress_file_gauge, TRUE);
+
+ if (!force_update)
+ return;
+
+ if (!ui->showing_eta || ctx->eta_secs <= 0.5)
+ label_set_text (ui->progress_file_label, stalled_msg);
+ else
+ {
+ char buffer2[BUF_TINY];
+
+ file_eta_prepare_for_show (buffer2, ctx->eta_secs, FALSE);
+ if (ctx->bps == 0)
+ label_set_textv (ui->progress_file_label, "%s %s", buffer2, stalled_msg);
+ else
+ {
+ char buffer3[BUF_TINY];
+
+ file_bps_prepare_for_show (buffer3, ctx->bps);
+ label_set_textv (ui->progress_file_label, "%s (%s) %s", buffer2, buffer3, stalled_msg);
+ }
+
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->total_files_processed_label == NULL)
+ return;
+
+ if (ctx->progress_totals_computed)
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu / %zu"), done,
+ total);
+ else
+ label_set_textv (ui->total_files_processed_label, _("Files processed: %zu"), done);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ uintmax_t copied_bytes, gboolean show_summary)
+{
+ char buffer2[BUF_TINY];
+ char buffer3[BUF_TINY];
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (ui->progress_total_gauge != NULL)
+ {
+ if (ctx->progress_bytes == 0)
+ gauge_show (ui->progress_total_gauge, FALSE);
+ else
+ {
+ gauge_set_value (ui->progress_total_gauge, 1024,
+ (int) (1024 * copied_bytes / ctx->progress_bytes));
+ gauge_show (ui->progress_total_gauge, TRUE);
+ }
+ }
+
+ if (!show_summary && tctx->bps == 0)
+ return;
+
+ if (ui->time_label != NULL)
+ {
+ gint64 tv_current;
+ char buffer4[BUF_TINY];
+
+ tv_current = g_get_monotonic_time ();
+ file_frmt_time (buffer2, (tv_current - tctx->transfer_start) / G_USEC_PER_SEC);
+
+ if (ctx->progress_totals_computed)
+ {
+ file_eta_prepare_for_show (buffer3, tctx->eta_secs, TRUE);
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s %s"), buffer2, buffer3);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s %s (%s)"), buffer2, buffer3, buffer4);
+ }
+ }
+ else
+ {
+ if (tctx->bps == 0)
+ label_set_textv (ui->time_label, _("Time: %s"), buffer2);
+ else
+ {
+ file_bps_prepare_for_show (buffer4, (long) tctx->bps);
+ label_set_textv (ui->time_label, _("Time: %s (%s)"), buffer2, buffer4);
+ }
+ }
+ }
+
+ if (ui->total_bytes_label != NULL)
+ {
+ size_trunc_len (buffer2, 5, tctx->copied_bytes, 0, panels_options.kilobyte_si);
+
+ if (!ctx->progress_totals_computed)
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s "), buffer2);
+ else
+ {
+ size_trunc_len (buffer3, 5, ctx->progress_bytes, 0, panels_options.kilobyte_si);
+ hline_set_textv (ui->total_bytes_label, _(" Total: %s / %s "), buffer2, buffer3);
+ }
+ }
+}
+
+/* }}} */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ char *s;
+
+ s = vfs_path_tokens_get (vpath, -1, 1);
+ label_set_text (ui->src_file_label, _("Source"));
+ label_set_text (ui->src_file, truncFileString (ui->op_dlg, s));
+ g_free (s);
+ }
+ else
+ {
+ label_set_text (ui->src_file_label, NULL);
+ label_set_text (ui->src_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath)
+{
+ file_op_context_ui_t *ui;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return;
+
+ ui = ctx->ui;
+
+ if (vpath != NULL)
+ {
+ label_set_text (ui->tgt_file_label, _("Target"));
+ label_set_text (ui->tgt_file, truncFileStringSecure (ui->op_dlg, vfs_path_as_str (vpath)));
+ }
+ else
+ {
+ label_set_text (ui->tgt_file_label, NULL);
+ label_set_text (ui->tgt_file, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+file_progress_show_deleting (file_op_context_t * ctx, const char *s, size_t * count)
+{
+ static gint64 timestamp = 0;
+ /* update with 25 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 25;
+
+ gboolean ret;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FALSE;
+
+ ret = mc_time_elapsed (&timestamp, delay);
+
+ if (ret)
+ {
+ file_op_context_ui_t *ui;
+
+ ui = ctx->ui;
+
+ if (ui->src_file_label != NULL)
+ label_set_text (ui->src_file_label, _("Deleting"));
+
+ label_set_text (ui->src_file, truncFileStringSecure (ui->op_dlg, s));
+ }
+
+ if (count != NULL)
+ (*count)++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+FileProgressStatus
+file_progress_real_query_replace (file_op_context_t * ctx, enum OperationMode mode,
+ const char *src, struct stat * src_stat,
+ const char *dst, struct stat * dst_stat)
+{
+ file_op_context_ui_t *ui;
+ FileProgressStatus replace_with_zero;
+
+ if (ctx == NULL || ctx->ui == NULL)
+ return FILE_CONT;
+
+ ui = ctx->ui;
+
+ if (ui->replace_result == REPLACE_YES || ui->replace_result == REPLACE_NO
+ || ui->replace_result == REPLACE_APPEND)
+ {
+ ui->src_filename = src;
+ ui->src_stat = src_stat;
+ ui->tgt_filename = dst;
+ ui->dst_stat = dst_stat;
+ ui->replace_result = overwrite_query_dialog (ctx, mode);
+ }
+
+ replace_with_zero = (src_stat->st_size == 0
+ && ui->dont_overwrite_with_zero) ? FILE_SKIP : FILE_CONT;
+
+ switch (ui->replace_result)
+ {
+ case REPLACE_OLDER:
+ do_refresh ();
+ if (src_stat->st_mtime > dst_stat->st_mtime)
+ return replace_with_zero;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_SIZE:
+ do_refresh ();
+ if (src_stat->st_size == dst_stat->st_size)
+ return FILE_SKIP;
+ else
+ return replace_with_zero;
+
+ case REPLACE_SMALLER:
+ do_refresh ();
+ if (src_stat->st_size > dst_stat->st_size)
+ return FILE_CONT;
+ else
+ return FILE_SKIP;
+
+ case REPLACE_ALL:
+ do_refresh ();
+ return replace_with_zero;
+
+ case REPLACE_REGET:
+ /* Careful: we fall through and set do_append */
+ ctx->do_reget = dst_stat->st_size;
+ MC_FALLTHROUGH;
+
+ case REPLACE_APPEND:
+ ctx->do_append = TRUE;
+ MC_FALLTHROUGH;
+
+ case REPLACE_YES:
+ do_refresh ();
+ return FILE_CONT;
+
+ case REPLACE_NO:
+ case REPLACE_NONE:
+ do_refresh ();
+ return FILE_SKIP;
+
+ case REPLACE_ABORT:
+ default:
+ return FILE_ABORT;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+file_mask_dialog (file_op_context_t * ctx, gboolean only_one, const char *format, const void *text,
+ const char *def_text, gboolean * do_bg)
+{
+ size_t fmd_xlen;
+ vfs_path_t *vpath;
+ gboolean source_easy_patterns = easy_patterns;
+ char fmd_buf[BUF_MEDIUM];
+ char *dest_dir = NULL;
+ char *tmp;
+ char *def_text_secure;
+
+ if (ctx == NULL)
+ return NULL;
+
+ /* unselect checkbox if target filesystem doesn't support attributes */
+ ctx->op_preserve = copymove_persistent_attr && filegui__check_attrs_on_fs (def_text);
+ ctx->stable_symlinks = FALSE;
+ *do_bg = FALSE;
+
+ /* filter out a possible password from def_text */
+ vpath = vfs_path_from_str_flags (def_text, only_one ? VPF_NO_CANON : VPF_NONE);
+ tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ vfs_path_free (vpath, TRUE);
+
+ if (source_easy_patterns)
+ def_text_secure = strutils_glob_escape (tmp);
+ else
+ def_text_secure = strutils_regex_escape (tmp);
+ g_free (tmp);
+
+ if (only_one)
+ {
+ int format_len, text_len;
+ int max_len;
+
+ format_len = str_term_width1 (format);
+ text_len = str_term_width1 (text);
+ max_len = COLS - 2 - 6;
+
+ if (format_len + text_len <= max_len)
+ {
+ fmd_xlen = format_len + text_len + 6;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ }
+ else
+ {
+ text = str_trunc ((const char *) text, max_len - format_len);
+ fmd_xlen = max_len + 6;
+ }
+
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, (const char *) text);
+ }
+ else
+ {
+ fmd_xlen = COLS * 2 / 3;
+ fmd_xlen = MAX (fmd_xlen, 68);
+ g_snprintf (fmd_buf, sizeof (fmd_buf), format, *(const int *) text);
+ }
+
+ {
+ char *source_mask = NULL;
+ char *orig_mask;
+ int val;
+ struct stat buf;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (fmd_buf, input_label_above, easy_patterns ? "*" : "^(.*)$",
+ "input-def", &source_mask, NULL, FALSE, FALSE,
+ INPUT_COMPLETE_FILENAMES),
+ QUICK_START_COLUMNS,
+ QUICK_SEPARATOR (FALSE),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("&Using shell patterns"), &source_easy_patterns, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_LABELED_INPUT (N_("to:"), input_label_above, def_text_secure, "input2", &dest_dir,
+ NULL, FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_SEPARATOR (TRUE),
+ QUICK_START_COLUMNS,
+ QUICK_CHECKBOX (N_("Follow &links"), &ctx->follow_links, NULL),
+ QUICK_CHECKBOX (N_("Preserve &attributes"), &ctx->op_preserve, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("Di&ve into subdir if exists"), &ctx->dive_into_subdirs, NULL),
+ QUICK_CHECKBOX (N_("&Stable symlinks"), &ctx->stable_symlinks, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL),
+#ifdef ENABLE_BACKGROUND
+ QUICK_BUTTON (N_("&Background"), B_USER, NULL, NULL),
+#endif /* ENABLE_BACKGROUND */
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, fmd_xlen };
+
+ quick_dialog_t qdlg = {
+ r, op_names[ctx->operation], "[Mask Copy/Rename]",
+ quick_widgets, NULL, NULL
+ };
+
+ while (TRUE)
+ {
+ val = quick_dialog_skip (&qdlg, 4);
+
+ if (val == B_CANCEL)
+ {
+ g_free (def_text_secure);
+ return NULL;
+ }
+
+ ctx->stat_func = ctx->follow_links ? mc_stat : mc_lstat;
+
+ if (ctx->op_preserve)
+ {
+ ctx->preserve = TRUE;
+ ctx->umask_kill = 0777777;
+ ctx->preserve_uidgid = (geteuid () == 0);
+ }
+ else
+ {
+ mode_t i2;
+
+ ctx->preserve = ctx->preserve_uidgid = FALSE;
+ i2 = umask (0);
+ umask (i2);
+ ctx->umask_kill = i2 ^ 0777777;
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (def_text_secure);
+ g_free (source_mask);
+ g_free (dest_dir);
+ return NULL;
+ }
+
+ ctx->search_handle = mc_search_new (source_mask, NULL);
+ if (ctx->search_handle != NULL)
+ break;
+
+ message (D_ERROR, MSG_ERROR, _("Invalid source pattern '%s'"), source_mask);
+ MC_PTR_FREE (dest_dir);
+ MC_PTR_FREE (source_mask);
+ }
+
+ g_free (def_text_secure);
+ g_free (source_mask);
+
+ ctx->search_handle->is_case_sensitive = TRUE;
+ if (source_easy_patterns)
+ ctx->search_handle->search_type = MC_SEARCH_T_GLOB;
+ else
+ ctx->search_handle->search_type = MC_SEARCH_T_REGEX;
+
+ tmp = dest_dir;
+ dest_dir = tilde_expand (tmp);
+ g_free (tmp);
+ vpath = vfs_path_from_str (dest_dir);
+
+ ctx->dest_mask = strrchr (dest_dir, PATH_SEP);
+ if (ctx->dest_mask == NULL)
+ ctx->dest_mask = dest_dir;
+ else
+ ctx->dest_mask++;
+
+ orig_mask = ctx->dest_mask;
+
+ if (*ctx->dest_mask == '\0'
+ || (!ctx->dive_into_subdirs && !is_wildcarded (ctx->dest_mask)
+ && (!only_one
+ || (mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode))))
+ || (ctx->dive_into_subdirs
+ && ((!only_one && !is_wildcarded (ctx->dest_mask))
+ || (only_one && mc_stat (vpath, &buf) == 0 && S_ISDIR (buf.st_mode)))))
+ ctx->dest_mask = g_strdup ("\\0");
+ else
+ {
+ ctx->dest_mask = g_strdup (ctx->dest_mask);
+ *orig_mask = '\0';
+ }
+
+ if (*dest_dir == '\0')
+ {
+ g_free (dest_dir);
+ dest_dir = g_strdup ("./");
+ }
+
+ vfs_path_free (vpath, TRUE);
+
+ if (val == B_USER)
+ *do_bg = TRUE;
+ }
+
+ return dest_dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filegui.h b/src/filemanager/filegui.h
new file mode 100644
index 0000000..6387faa
--- /dev/null
+++ b/src/filemanager/filegui.h
@@ -0,0 +1,40 @@
+/** \file filegui.h
+ * \brief Header: file management GUI for the text mode edition
+ */
+
+#ifndef MC__FILEGUI_H
+#define MC__FILEGUI_H
+
+#include "lib/global.h"
+#include "fileopctx.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void file_op_context_create_ui (file_op_context_t * ctx, gboolean with_eta,
+ filegui_dialog_type_t dialog_type);
+void file_op_context_destroy_ui (file_op_context_t * ctx);
+
+char *file_mask_dialog (file_op_context_t * ctx, gboolean only_one, const char *format,
+ const void *text, const char *def_text, gboolean * do_bg);
+
+FileProgressStatus check_progress_buttons (file_op_context_t * ctx);
+
+void file_progress_show (file_op_context_t * ctx, off_t done, off_t total,
+ const char *stalled_msg, gboolean force_update);
+void file_progress_show_count (file_op_context_t * ctx, size_t done, size_t total);
+void file_progress_show_total (file_op_total_context_t * tctx, file_op_context_t * ctx,
+ uintmax_t copied_bytes, gboolean show_summary);
+void file_progress_show_source (file_op_context_t * ctx, const vfs_path_t * vpath);
+void file_progress_show_target (file_op_context_t * ctx, const vfs_path_t * vpath);
+gboolean file_progress_show_deleting (file_op_context_t * ctx, const char *path, size_t * count);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILEGUI_H */
diff --git a/src/filemanager/filemanager.c b/src/filemanager/filemanager.c
new file mode 100644
index 0000000..b995024
--- /dev/null
+++ b/src/filemanager/filemanager.c
@@ -0,0 +1,1852 @@
+/*
+ Main dialog (file panels) of the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1994, 1995, 1996, 1997
+ Janne Kukonlehto, 1994, 1995
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2009-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file filemanager.c
+ * \brief Source: main dialog (file panels) of the Midnight Commander
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pwd.h> /* for username in xterm title */
+
+#include "lib/global.h"
+#include "lib/fileloc.h" /* MC_HINT */
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* KEY_M_* masks */
+#include "lib/skin.h"
+#include "lib/util.h"
+
+#include "lib/vfs/vfs.h"
+
+#include "src/args.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+#include "src/execute.h" /* toggle_subshell */
+#include "src/setup.h" /* variables */
+#include "src/learn.h" /* learn_keys() */
+#include "src/keymap.h"
+#include "lib/fileloc.h" /* MC_FILEPOS_FILE */
+#include "lib/keybind.h"
+#include "lib/event.h"
+
+#include "tree.h"
+#include "boxes.h" /* sort_box(), tree_box() */
+#include "layout.h"
+#include "cmd.h" /* commands */
+#include "hotlist.h"
+#include "panelize.h"
+#include "command.h" /* cmdline */
+#include "dir.h" /* dir_list_clean() */
+
+#ifdef USE_INTERNAL_EDIT
+#include "src/editor/edit.h"
+#endif
+
+#ifdef USE_DIFF_VIEW
+#include "src/diffviewer/ydiff.h"
+#endif
+
+#include "src/consaver/cons.saver.h" /* show_console_contents */
+#include "src/file_history.h" /* show_file_history() */
+
+#include "filemanager.h"
+
+/*** global variables ****************************************************************************/
+
+/* When the modes are active, left_panel, right_panel and tree_panel */
+/* point to a proper data structure. You should check with the functions */
+/* get_current_type and get_other_type the types of the panels before using */
+/* this pointer variables */
+
+/* The structures for the panels */
+WPanel *left_panel = NULL;
+WPanel *right_panel = NULL;
+/* Pointer to the selected and unselected panel */
+WPanel *current_panel = NULL;
+
+/* The Menubar */
+WMenuBar *the_menubar = NULL;
+/* The widget where we draw the prompt */
+WLabel *the_prompt;
+/* The hint bar */
+WLabel *the_hint;
+/* The button bar */
+WButtonBar *the_bar;
+
+/* The prompt */
+const char *mc_prompt = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef HAVE_CHARSET
+/*
+ * Don't restrict the output on the screen manager level,
+ * the translation tables take care of it.
+ */
+#endif /* !HAVE_CHARSET */
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static menu_t *left_menu, *right_menu;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Stop MC main dialog and the current dialog if it exists.
+ * Needed to provide fast exit from MC viewer or editor on shell exit */
+static void
+stop_dialogs (void)
+{
+ dlg_close (filemanager);
+
+ if (top_dlg != NULL)
+ dlg_close (DIALOG (top_dlg->data));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+treebox_cmd (void)
+{
+ char *sel_dir;
+
+ sel_dir = tree_box (panel_current_entry (current_panel)->fname->str);
+ if (sel_dir != NULL)
+ {
+ vfs_path_t *sel_vdir;
+
+ sel_vdir = vfs_path_from_str (sel_dir);
+ panel_cd (current_panel, sel_vdir, cd_exact);
+ vfs_path_free (sel_vdir, TRUE);
+ g_free (sel_dir);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef LISTMODE_EDITOR
+static void
+listmode_cmd (void)
+{
+ char *newmode;
+
+ if (get_current_type () != view_listing)
+ return;
+
+ newmode = listmode_edit (current_panel->user_format);
+ if (!newmode)
+ return;
+
+ g_free (current_panel->user_format);
+ current_panel->list_format = list_user;
+ current_panel->user_format = newmode;
+ set_panel_formats (current_panel);
+
+ do_refresh ();
+}
+#endif /* LISTMODE_EDITOR */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_panel_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("File listin&g"), CK_PanelListing));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quick view"), CK_PanelQuickView));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Info"), CK_PanelInfo));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Tree"), CK_PanelTree));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Listing format..."), CK_SetupListingFormat));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Sort order..."), CK_Sort));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Filter..."), CK_Filter));
+#ifdef HAVE_CHARSET
+ entries = g_list_prepend (entries, menu_entry_new (_("&Encoding..."), CK_SelectCodepage));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef ENABLE_VFS_FTP
+ entries = g_list_prepend (entries, menu_entry_new (_("FT&P link..."), CK_ConnectFtp));
+#endif
+#ifdef ENABLE_VFS_FISH
+ entries = g_list_prepend (entries, menu_entry_new (_("S&hell link..."), CK_ConnectFish));
+#endif
+#ifdef ENABLE_VFS_SFTP
+ entries = g_list_prepend (entries, menu_entry_new (_("SFTP li&nk..."), CK_ConnectSftp));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("Paneli&ze"), CK_Panelize));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Rescan"), CK_Reread));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_file_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&View"), CK_View));
+ entries = g_list_prepend (entries, menu_entry_new (_("Vie&w file..."), CK_ViewFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Filtered view"), CK_ViewFiltered));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Edit"), CK_Edit));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Copy"), CK_Copy));
+ entries = g_list_prepend (entries, menu_entry_new (_("C&hmod"), CK_ChangeMode));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Link"), CK_Link));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Symlink"), CK_LinkSymbolic));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Relative symlin&k"), CK_LinkSymbolicRelative));
+ entries = g_list_prepend (entries, menu_entry_new (_("Edit s&ymlink"), CK_LinkSymbolicEdit));
+ entries = g_list_prepend (entries, menu_entry_new (_("Ch&own"), CK_ChangeOwn));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Advanced chown"), CK_ChangeOwnAdvanced));
+#ifdef ENABLE_EXT2FS_ATTR
+ entries = g_list_prepend (entries, menu_entry_new (_("Cha&ttr"), CK_ChangeAttributes));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("&Rename/Move"), CK_Move));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Mkdir"), CK_MakeDir));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Delete"), CK_Delete));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Quick cd"), CK_CdQuick));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Select &group"), CK_Select));
+ entries = g_list_prepend (entries, menu_entry_new (_("U&nselect group"), CK_Unselect));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Invert selection"), CK_SelectInvert));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("E&xit"), CK_Quit));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_command_menu (void)
+{
+ /* I know, I'm lazy, but the tree widget when it's not running
+ * as a panel still has some problems, I have not yet finished
+ * the WTree widget port, sorry.
+ */
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&User menu"), CK_UserMenu));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Directory tree"), CK_Tree));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Find file"), CK_Find));
+ entries = g_list_prepend (entries, menu_entry_new (_("S&wap panels"), CK_Swap));
+ entries = g_list_prepend (entries, menu_entry_new (_("Switch &panels on/off"), CK_Shell));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Compare directories"), CK_CompareDirs));
+#ifdef USE_DIFF_VIEW
+ entries = g_list_prepend (entries, menu_entry_new (_("C&ompare files"), CK_CompareFiles));
+#endif
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("E&xternal panelize"), CK_ExternalPanelize));
+ entries = g_list_prepend (entries, menu_entry_new (_("Show directory s&izes"), CK_DirSize));
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("Command &history"), CK_History));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Viewed/edited files hi&story"), CK_EditorViewerHistory));
+ entries = g_list_prepend (entries, menu_entry_new (_("Di&rectory hotlist"), CK_HotList));
+#ifdef ENABLE_VFS
+ entries = g_list_prepend (entries, menu_entry_new (_("&Active VFS list"), CK_VfsList));
+#endif
+#ifdef ENABLE_BACKGROUND
+ entries = g_list_prepend (entries, menu_entry_new (_("&Background jobs"), CK_Jobs));
+#endif
+ entries = g_list_prepend (entries, menu_entry_new (_("Screen lis&t"), CK_ScreenList));
+ entries = g_list_prepend (entries, menu_separator_new ());
+#ifdef ENABLE_VFS_UNDELFS
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Undelete files (ext2fs only)"), CK_Undelete));
+#endif
+#ifdef LISTMODE_EDITOR
+ entries = g_list_prepend (entries, menu_entry_new (_("&Listing format edit"), CK_ListMode));
+#endif
+#if defined (ENABLE_VFS_UNDELFS) || defined (LISTMODE_EDITOR)
+ entries = g_list_prepend (entries, menu_separator_new ());
+#endif
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("Edit &extension file"), CK_EditExtensionsFile));
+ entries = g_list_prepend (entries, menu_entry_new (_("Edit &menu file"), CK_EditUserMenu));
+ entries =
+ g_list_prepend (entries,
+ menu_entry_new (_("Edit hi&ghlighting group file"),
+ CK_EditFileHighlightFile));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+create_options_menu (void)
+{
+ GList *entries = NULL;
+
+ entries = g_list_prepend (entries, menu_entry_new (_("&Configuration..."), CK_Options));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Layout..."), CK_OptionsLayout));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Panel options..."), CK_OptionsPanel));
+ entries = g_list_prepend (entries, menu_entry_new (_("C&onfirmation..."), CK_OptionsConfirm));
+ entries = g_list_prepend (entries, menu_entry_new (_("&Appearance..."), CK_OptionsAppearance));
+ entries =
+ g_list_prepend (entries, menu_entry_new (_("&Display bits..."), CK_OptionsDisplayBits));
+ entries = g_list_prepend (entries, menu_entry_new (_("Learn &keys..."), CK_LearnKeys));
+#ifdef ENABLE_VFS
+ entries = g_list_prepend (entries, menu_entry_new (_("&Virtual FS..."), CK_OptionsVfs));
+#endif
+ entries = g_list_prepend (entries, menu_separator_new ());
+ entries = g_list_prepend (entries, menu_entry_new (_("&Save setup"), CK_SaveSetup));
+
+ return g_list_reverse (entries);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_menu (void)
+{
+ left_menu = menu_new ("", create_panel_menu (), "[Left and Right Menus]");
+ menubar_add_menu (the_menubar, left_menu);
+ menubar_add_menu (the_menubar, menu_new (_("&File"), create_file_menu (), "[File Menu]"));
+ menubar_add_menu (the_menubar,
+ menu_new (_("&Command"), create_command_menu (), "[Command Menu]"));
+ menubar_add_menu (the_menubar,
+ menu_new (_("&Options"), create_options_menu (), "[Options Menu]"));
+ right_menu = menu_new ("", create_panel_menu (), "[Left and Right Menus]");
+ menubar_add_menu (the_menubar, right_menu);
+ update_menu ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_last_selected_cmd (void)
+{
+ menubar_activate (the_menubar, drop_menus, -1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_cmd (void)
+{
+ int selected;
+
+ if ((get_current_index () == 0) == current_panel->active)
+ selected = 0;
+ else
+ selected = g_list_length (the_menubar->menu) - 1;
+
+ menubar_activate (the_menubar, drop_menus, selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+sort_cmd (void)
+{
+ WPanel *p;
+ const panel_field_t *sort_order;
+
+ if (!SELECTED_IS_PANEL)
+ return;
+
+ p = MENU_PANEL;
+ sort_order = sort_box (&p->sort_info, p->sort_field);
+ panel_set_sort_order (p, sort_order);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+midnight_get_shortcut (long command)
+{
+ const char *ext_map;
+ const char *shortcut = NULL;
+
+ shortcut = keybind_lookup_keymap_shortcut (filemanager_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ shortcut = keybind_lookup_keymap_shortcut (panel_map, command);
+ if (shortcut != NULL)
+ return g_strdup (shortcut);
+
+ ext_map = keybind_lookup_keymap_shortcut (filemanager_map, CK_ExtendedKeyMap);
+ if (ext_map != NULL)
+ shortcut = keybind_lookup_keymap_shortcut (filemanager_x_map, command);
+ if (shortcut != NULL)
+ return g_strdup_printf ("%s %s", ext_map, shortcut);
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+midnight_get_title (const WDialog * h, size_t len)
+{
+ char *path;
+ char *login;
+ char *p;
+
+ (void) h;
+
+ title_path_prepare (&path, &login);
+
+ p = g_strdup_printf ("%s [%s]:%s", _("Panels:"), login, path);
+ g_free (path);
+ g_free (login);
+ path = g_strdup (str_trunc (p, len - 4));
+ g_free (p);
+
+ return path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+toggle_panels_split (void)
+{
+ panels_layout.horizontal_split = !panels_layout.horizontal_split;
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+/* event helper */
+static gboolean
+check_panel_timestamp (const WPanel * panel, panel_view_mode_t mode, struct vfs_class *vclass,
+ vfsid id)
+{
+ if (mode == view_listing)
+ {
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (panel->cwd_vpath);
+ if (me != vclass)
+ return FALSE;
+
+ if (vfs_getid (panel->cwd_vpath) != id)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+check_current_panel_timestamp (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret =
+ check_panel_timestamp (current_panel, get_current_type (), event_data->vclass,
+ event_data->id);
+ return !event_data->ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+check_other_panel_timestamp (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_stamp_create_t *event_data = (ev_vfs_stamp_create_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ event_data->ret =
+ check_panel_timestamp (other_panel, get_other_type (), event_data->vclass, event_data->id);
+ return !event_data->ret;
+}
+#endif /* ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+print_vfs_message (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ ev_vfs_print_message_t *event_data = (ev_vfs_print_message_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+
+ if (mc_global.midnight_shutdown)
+ goto ret;
+
+ if (!mc_global.message_visible || the_hint == NULL || WIDGET (the_hint)->owner == NULL)
+ {
+ int col, row;
+
+ if (!nice_rotating_dash || (ok_to_refresh <= 0))
+ goto ret;
+
+ /* Preserve current cursor position */
+ tty_getyx (&row, &col);
+
+ tty_gotoyx (0, 0);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (str_fit_to_term (event_data->msg, COLS - 1, J_LEFT));
+
+ /* Restore cursor position */
+ tty_gotoyx (row, col);
+ mc_refresh ();
+ goto ret;
+ }
+
+ if (mc_global.message_visible)
+ set_hintbar (event_data->msg);
+
+ ret:
+ MC_PTR_FREE (event_data->msg);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+create_panels (void)
+{
+ int current_index, other_index;
+ panel_view_mode_t current_mode, other_mode;
+ char *current_dir, *other_dir;
+ vfs_path_t *original_dir;
+
+ /*
+ * Following cases from command line are possible:
+ * 'mc' (no arguments): mc_run_param0 == NULL, mc_run_param1 == NULL
+ * active panel uses current directory
+ * passive panel uses "other_dir" from panels.ini
+ *
+ * 'mc dir1 dir2' (two arguments): mc_run_param0 != NULL, mc_run_param1 != NULL
+ * active panel uses mc_run_param0
+ * passive panel uses mc_run_param1
+ *
+ * 'mc dir1' (single argument): mc_run_param0 != NULL, mc_run_param1 == NULL
+ * active panel uses mc_run_param0
+ * passive panel uses "other_dir" from panels.ini
+ */
+
+ /* Set up panel directories */
+ if (boot_current_is_left)
+ {
+ /* left panel is active */
+ current_index = 0;
+ other_index = 1;
+ current_mode = startup_left_mode;
+ other_mode = startup_right_mode;
+
+ if (mc_run_param0 == NULL && mc_run_param1 == NULL)
+ {
+ /* no arguments */
+ current_dir = NULL; /* assume current dir */
+ other_dir = saved_other_dir; /* from ini */
+ }
+ else if (mc_run_param0 != NULL && mc_run_param1 != NULL)
+ {
+ /* two arguments */
+ current_dir = (char *) mc_run_param0;
+ other_dir = mc_run_param1;
+ }
+ else /* mc_run_param0 != NULL && mc_run_param1 == NULL */
+ {
+ /* one argument */
+ current_dir = (char *) mc_run_param0;
+ other_dir = saved_other_dir; /* from ini */
+ }
+ }
+ else
+ {
+ /* right panel is active */
+ current_index = 1;
+ other_index = 0;
+ current_mode = startup_right_mode;
+ other_mode = startup_left_mode;
+
+ if (mc_run_param0 == NULL && mc_run_param1 == NULL)
+ {
+ /* no arguments */
+ current_dir = NULL; /* assume current dir */
+ other_dir = saved_other_dir; /* from ini */
+ }
+ else if (mc_run_param0 != NULL && mc_run_param1 != NULL)
+ {
+ /* two arguments */
+ current_dir = (char *) mc_run_param0;
+ other_dir = mc_run_param1;
+ }
+ else /* mc_run_param0 != NULL && mc_run_param1 == NULL */
+ {
+ /* one argument */
+ current_dir = (char *) mc_run_param0;
+ other_dir = saved_other_dir; /* from ini */
+ }
+ }
+
+ /* 1. Get current dir */
+ original_dir = vfs_path_clone (vfs_get_raw_current_dir ());
+
+ /* 2. Create passive panel */
+ if (other_dir != NULL)
+ {
+ vfs_path_t *vpath;
+
+ if (g_path_is_absolute (other_dir))
+ vpath = vfs_path_from_str (other_dir);
+ else
+ vpath = vfs_path_append_new (original_dir, other_dir, (char *) NULL);
+ mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ create_panel (other_index, other_mode);
+
+ /* 3. Create active panel */
+ if (current_dir == NULL)
+ mc_chdir (original_dir);
+ else
+ {
+ vfs_path_t *vpath;
+
+ if (g_path_is_absolute (current_dir))
+ vpath = vfs_path_from_str (current_dir);
+ else
+ vpath = vfs_path_append_new (original_dir, current_dir, (char *) NULL);
+ mc_chdir (vpath);
+ vfs_path_free (vpath, TRUE);
+ }
+ create_panel (current_index, current_mode);
+
+ if (startup_left_mode == view_listing)
+ current_panel = left_panel;
+ else if (right_panel != NULL)
+ current_panel = right_panel;
+ else
+ current_panel = left_panel;
+
+ vfs_path_free (original_dir, TRUE);
+
+#ifdef ENABLE_VFS
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_other_panel_timestamp, NULL, NULL);
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_timestamp", check_current_panel_timestamp, NULL, NULL);
+#endif /* ENABLE_VFS */
+
+ mc_event_add (MCEVENT_GROUP_CORE, "vfs_print_message", print_vfs_message, NULL, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+midnight_put_panel_path (WPanel * panel)
+{
+ vfs_path_t *cwd_vpath;
+ const char *cwd_vpath_str;
+
+ if (!command_prompt)
+ return;
+
+#ifdef HAVE_CHARSET
+ cwd_vpath = remove_encoding_from_path (panel->cwd_vpath);
+#else
+ cwd_vpath = vfs_path_clone (panel->cwd_vpath);
+#endif
+
+ cwd_vpath_str = vfs_path_as_str (cwd_vpath);
+
+ command_insert (cmdline, cwd_vpath_str, FALSE);
+
+ if (!IS_PATH_SEP (cwd_vpath_str[strlen (cwd_vpath_str) - 1]))
+ command_insert (cmdline, PATH_SEP_STR, FALSE);
+
+ vfs_path_free (cwd_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_link (WPanel * panel)
+{
+ const file_entry_t *fe;
+
+ if (!command_prompt)
+ return;
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISLNK (fe->st.st_mode))
+ {
+ char buffer[MC_MAXPATHLEN];
+ vfs_path_t *vpath;
+ int i;
+
+ vpath = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ i = mc_readlink (vpath, buffer, sizeof (buffer) - 1);
+ vfs_path_free (vpath, TRUE);
+
+ if (i > 0)
+ {
+ buffer[i] = '\0';
+ command_insert (cmdline, buffer, TRUE);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_current_link (void)
+{
+ put_link (current_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_other_link (void)
+{
+ if (get_other_type () == view_listing)
+ put_link (other_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Insert the selected file name into the input line */
+static void
+put_current_selected (void)
+{
+ const char *tmp;
+
+ if (!command_prompt)
+ return;
+
+ if (get_current_type () == view_tree)
+ {
+ WTree *tree;
+ const vfs_path_t *selected_name;
+
+ tree = (WTree *) get_panel_widget (get_current_index ());
+ selected_name = tree_selected_name (tree);
+ tmp = vfs_path_as_str (selected_name);
+ }
+ else
+ tmp = panel_current_entry (current_panel)->fname->str;
+
+ command_insert (cmdline, tmp, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_tagged (WPanel * panel)
+{
+ if (!command_prompt)
+ return;
+ input_disable_update (cmdline);
+ if (panel->marked)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ command_insert (cmdline, panel->dir.list[i].fname->str, TRUE);
+ }
+ else
+ command_insert (cmdline, panel_current_entry (panel)->fname->str, TRUE);
+
+ input_enable_update (cmdline);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_current_tagged (void)
+{
+ put_tagged (current_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+put_other_tagged (void)
+{
+ if (get_other_type () == view_listing)
+ put_tagged (other_panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_mc (void)
+{
+#ifdef HAVE_SLANG
+#ifdef HAVE_CHARSET
+ tty_display_8bit (TRUE);
+#else
+ tty_display_8bit (mc_global.full_eight_bits);
+#endif /* HAVE_CHARSET */
+
+#else /* HAVE_SLANG */
+
+#ifdef HAVE_CHARSET
+ tty_display_8bit (TRUE);
+#else
+ tty_display_8bit (mc_global.eight_bit_clean);
+#endif /* HAVE_CHARSET */
+#endif /* HAVE_SLANG */
+
+ if ((tty_baudrate () < 9600) || mc_global.tty.slow_terminal)
+ verbose = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_dummy_mc (void)
+{
+ vfs_path_t *vpath;
+ char *d;
+ int ret;
+
+ d = vfs_get_cwd ();
+ setup_mc ();
+ vpath = vfs_path_from_str (d);
+ ret = mc_chdir (vpath);
+ (void) ret;
+ vfs_path_free (vpath, TRUE);
+ g_free (d);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+done_mc (void)
+{
+ /* Setup shutdown
+ *
+ * We sync the profiles since the hotlist may have changed, while
+ * we only change the setup data if we have the auto save feature set
+ */
+
+ save_setup (auto_save_setup, panels_options.auto_save_setup);
+
+ vfs_stamp_path (vfs_get_raw_current_dir ());
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+create_file_manager (void)
+{
+ Widget *w = WIDGET (filemanager);
+ WGroup *g = GROUP (filemanager);
+
+ w->keymap = filemanager_map;
+ w->ext_keymap = filemanager_x_map;
+
+ filemanager->get_shortcut = midnight_get_shortcut;
+ filemanager->get_title = midnight_get_title;
+ /* allow rebind tab */
+ widget_want_tab (w, TRUE);
+
+ the_menubar = menubar_new (NULL);
+ group_add_widget (g, the_menubar);
+ init_menu ();
+
+ create_panels ();
+ group_add_widget (g, get_panel_widget (0));
+ group_add_widget (g, get_panel_widget (1));
+
+ the_hint = label_new (0, 0, NULL);
+ the_hint->transparent = TRUE;
+ the_hint->auto_adjust_cols = 0;
+ WIDGET (the_hint)->rect.cols = COLS;
+ group_add_widget (g, the_hint);
+
+ cmdline = command_new (0, 0, 0);
+ group_add_widget (g, cmdline);
+
+ the_prompt = label_new (0, 0, mc_prompt);
+ the_prompt->transparent = TRUE;
+ group_add_widget (g, the_prompt);
+
+ the_bar = buttonbar_new ();
+ group_add_widget (g, the_bar);
+ midnight_set_buttonbar (the_bar);
+
+#ifdef ENABLE_SUBSHELL
+ /* Must be done after creation of cmdline and prompt widgets to avoid potential
+ NULL dereference in load_prompt() -> ... -> setup_cmdline() -> label_set_text(). */
+ if (mc_global.tty.use_subshell)
+ add_select_channel (mc_global.tty.subshell_pty, load_prompt, NULL);
+#endif /* !ENABLE_SUBSHELL */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** result must be free'd (I think this should go in util.c) */
+static vfs_path_t *
+prepend_cwd_on_local (const char *filename)
+{
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (filename);
+ if (!vfs_file_is_local (vpath) || g_path_is_absolute (filename))
+ return vpath;
+
+ vfs_path_free (vpath, TRUE);
+
+ return vfs_path_append_new (vfs_get_raw_current_dir (), filename, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Invoke the internal view/edit routine with:
+ * the default processing and forcing the internal viewer/editor
+ */
+static gboolean
+mc_maybe_editor_or_viewer (void)
+{
+ gboolean ret;
+
+ switch (mc_global.mc_run_mode)
+ {
+#ifdef USE_INTERNAL_EDIT
+ case MC_RUN_EDITOR:
+ ret = edit_files ((GList *) mc_run_param0);
+ break;
+#endif /* USE_INTERNAL_EDIT */
+ case MC_RUN_VIEWER:
+ {
+ vfs_path_t *vpath = NULL;
+
+ if (mc_run_param0 != NULL && *(char *) mc_run_param0 != '\0')
+ vpath = prepend_cwd_on_local ((char *) mc_run_param0);
+
+ ret = view_file (vpath, FALSE, TRUE);
+ vfs_path_free (vpath, TRUE);
+ break;
+ }
+#ifdef USE_DIFF_VIEW
+ case MC_RUN_DIFFVIEWER:
+ ret = dview_diff_cmd (mc_run_param0, mc_run_param1);
+ break;
+#endif /* USE_DIFF_VIEW */
+ default:
+ ret = FALSE;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_editor_viewer_history (void)
+{
+ char *s;
+ int act;
+
+ s = show_file_history (WIDGET (filemanager), &act);
+ if (s != NULL)
+ {
+ vfs_path_t *s_vpath;
+
+ switch (act)
+ {
+ case CK_Edit:
+ s_vpath = vfs_path_from_str (s);
+ edit_file_at_line (s_vpath, use_internal_edit, 0);
+ break;
+
+ case CK_View:
+ s_vpath = vfs_path_from_str (s);
+ view_file (s_vpath, use_internal_view, FALSE);
+ break;
+
+ default:
+ {
+ char *d;
+
+ d = g_path_get_dirname (s);
+ s_vpath = vfs_path_from_str (d);
+ panel_cd (current_panel, s_vpath, cd_exact);
+ panel_set_current_by_name (current_panel, s);
+ g_free (d);
+ }
+ }
+
+ g_free (s);
+ vfs_path_free (s_vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+quit_cmd_internal (int quiet)
+{
+ int q = quit;
+ size_t n;
+
+ n = dialog_switch_num () - 1;
+ if (n != 0)
+ {
+ char msg[BUF_MEDIUM];
+
+ g_snprintf (msg, sizeof (msg),
+ ngettext ("You have %zu opened screen. Quit anyway?",
+ "You have %zu opened screens. Quit anyway?", n), n);
+
+ if (query_dialog (_("The Midnight Commander"), msg, D_NORMAL, 2, _("&Yes"), _("&No")) != 0)
+ return FALSE;
+ q = 1;
+ }
+ else if (quiet || !confirm_exit)
+ q = 1;
+ else if (query_dialog (_("The Midnight Commander"),
+ _("Do you really want to quit the Midnight Commander?"),
+ D_NORMAL, 2, _("&Yes"), _("&No")) == 0)
+ q = 1;
+
+ if (q != 0)
+ {
+#ifdef ENABLE_SUBSHELL
+ if (!mc_global.tty.use_subshell)
+ stop_dialogs ();
+ else if ((q = exit_subshell ()? 1 : 0) != 0)
+#endif
+ stop_dialogs ();
+ }
+
+ if (q != 0)
+ quit |= 1;
+ return (quit != 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+quit_cmd (void)
+{
+ return quit_cmd_internal (0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Repaint the contents of the panels without frames. To schedule panel
+ * for repainting, set panel->dirty to TRUE. There are many reasons why
+ * the panels need to be repainted, and this is a costly operation, so
+ * it's done once per event.
+ */
+
+static void
+update_dirty_panels (void)
+{
+ if (get_current_type () == view_listing && current_panel->dirty)
+ widget_draw (WIDGET (current_panel));
+
+ if (get_other_type () == view_listing && other_panel->dirty)
+ widget_draw (WIDGET (other_panel));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+toggle_show_hidden (void)
+{
+ panels_options.show_dot_files = !panels_options.show_dot_files;
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+ /* redraw panels forced */
+ update_dirty_panels ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+midnight_execute_cmd (Widget * sender, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ (void) sender;
+
+ /* stop quick search before executing any command */
+ send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL);
+
+ switch (command)
+ {
+ case CK_ChangePanel:
+ (void) change_panel ();
+ break;
+ case CK_HotListAdd:
+ add2hotlist_cmd (current_panel);
+ break;
+ case CK_SetupListingFormat:
+ setup_listing_format_cmd ();
+ break;
+ case CK_ChangeMode:
+ chmod_cmd (current_panel);
+ break;
+ case CK_ChangeOwn:
+ chown_cmd (current_panel);
+ break;
+ case CK_ChangeOwnAdvanced:
+ advanced_chown_cmd (current_panel);
+ break;
+#ifdef ENABLE_EXT2FS_ATTR
+ case CK_ChangeAttributes:
+ chattr_cmd (current_panel);
+ break;
+#endif
+ case CK_CompareDirs:
+ compare_dirs_cmd ();
+ break;
+ case CK_Options:
+ configure_box ();
+ break;
+#ifdef ENABLE_VFS
+ case CK_OptionsVfs:
+ configure_vfs_box ();
+ break;
+#endif
+ case CK_OptionsConfirm:
+ confirm_box ();
+ break;
+ case CK_Copy:
+ copy_cmd (current_panel);
+ break;
+ case CK_PutCurrentPath:
+ midnight_put_panel_path (current_panel);
+ break;
+ case CK_PutCurrentSelected:
+ put_current_selected ();
+ break;
+ case CK_PutCurrentFullSelected:
+ midnight_put_panel_path (current_panel);
+ put_current_selected ();
+ break;
+ case CK_PutCurrentLink:
+ put_current_link ();
+ break;
+ case CK_PutCurrentTagged:
+ put_current_tagged ();
+ break;
+ case CK_PutOtherPath:
+ if (get_other_type () == view_listing)
+ midnight_put_panel_path (other_panel);
+ break;
+ case CK_PutOtherLink:
+ put_other_link ();
+ break;
+ case CK_PutOtherTagged:
+ put_other_tagged ();
+ break;
+ case CK_Delete:
+ delete_cmd (current_panel);
+ break;
+ case CK_ScreenList:
+ dialog_switch_list ();
+ break;
+#ifdef USE_DIFF_VIEW
+ case CK_CompareFiles:
+ diff_view_cmd ();
+ break;
+#endif
+ case CK_OptionsDisplayBits:
+ display_bits_box ();
+ break;
+ case CK_Edit:
+ edit_cmd (current_panel);
+ break;
+#ifdef USE_INTERNAL_EDIT
+ case CK_EditForceInternal:
+ edit_cmd_force_internal (current_panel);
+ break;
+#endif
+ case CK_EditExtensionsFile:
+ ext_cmd ();
+ break;
+ case CK_EditFileHighlightFile:
+ edit_fhl_cmd ();
+ break;
+ case CK_EditUserMenu:
+ edit_mc_menu_cmd ();
+ break;
+ case CK_LinkSymbolicEdit:
+ edit_symlink_cmd ();
+ break;
+ case CK_ExternalPanelize:
+ external_panelize_cmd ();
+ break;
+ case CK_ViewFiltered:
+ view_filtered_cmd (current_panel);
+ break;
+ case CK_Find:
+ find_cmd (current_panel);
+ break;
+#ifdef ENABLE_VFS_FISH
+ case CK_ConnectFish:
+ fishlink_cmd ();
+ break;
+#endif
+#ifdef ENABLE_VFS_FTP
+ case CK_ConnectFtp:
+ ftplink_cmd ();
+ break;
+#endif
+#ifdef ENABLE_VFS_SFTP
+ case CK_ConnectSftp:
+ sftplink_cmd ();
+ break;
+#endif
+ case CK_Panelize:
+ panel_panelize_cd ();
+ break;
+ case CK_Help:
+ help_cmd ();
+ break;
+ case CK_History:
+ /* show the history of command line widget */
+ send_message (cmdline, NULL, MSG_ACTION, CK_History, NULL);
+ break;
+ case CK_PanelInfo:
+ if (sender == WIDGET (the_menubar))
+ info_cmd (); /* menu */
+ else
+ info_cmd_no_menu (); /* shortcut or buttonbar */
+ break;
+#ifdef ENABLE_BACKGROUND
+ case CK_Jobs:
+ jobs_box ();
+ break;
+#endif
+ case CK_OptionsLayout:
+ layout_box ();
+ break;
+ case CK_OptionsAppearance:
+ appearance_box ();
+ break;
+ case CK_LearnKeys:
+ learn_keys ();
+ break;
+ case CK_Link:
+ link_cmd (LINK_HARDLINK);
+ break;
+ case CK_PanelListing:
+ listing_cmd ();
+ break;
+#ifdef LISTMODE_EDITOR
+ case CK_ListMode:
+ listmode_cmd ();
+ break;
+#endif
+ case CK_Menu:
+ menu_cmd ();
+ break;
+ case CK_MenuLastSelected:
+ menu_last_selected_cmd ();
+ break;
+ case CK_MakeDir:
+ mkdir_cmd (current_panel);
+ break;
+ case CK_OptionsPanel:
+ panel_options_box ();
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ encoding_cmd ();
+ break;
+#endif
+ case CK_CdQuick:
+ quick_cd_cmd (current_panel);
+ break;
+ case CK_HotList:
+ hotlist_cmd (current_panel);
+ break;
+ case CK_PanelQuickView:
+ if (sender == WIDGET (the_menubar))
+ quick_view_cmd (); /* menu */
+ else
+ quick_cmd_no_menu (); /* shortcut or buttonabr */
+ break;
+ case CK_QuitQuiet:
+ quiet_quit_cmd ();
+ break;
+ case CK_Quit:
+ quit_cmd ();
+ break;
+ case CK_LinkSymbolicRelative:
+ link_cmd (LINK_SYMLINK_RELATIVE);
+ break;
+ case CK_Move:
+ rename_cmd (current_panel);
+ break;
+ case CK_Reread:
+ reread_cmd ();
+ break;
+#ifdef ENABLE_VFS
+ case CK_VfsList:
+ vfs_list (current_panel);
+ break;
+#endif
+ case CK_SaveSetup:
+ save_setup_cmd ();
+ break;
+ case CK_Select:
+ case CK_Unselect:
+ case CK_SelectInvert:
+ case CK_Filter:
+ res = send_message (current_panel, filemanager, MSG_ACTION, command, NULL);
+ break;
+ case CK_Shell:
+ toggle_subshell ();
+ break;
+ case CK_DirSize:
+ smart_dirsize_cmd (current_panel);
+ break;
+ case CK_Sort:
+ sort_cmd ();
+ break;
+ case CK_ExtendedKeyMap:
+ WIDGET (filemanager)->ext_mode = TRUE;
+ break;
+ case CK_Suspend:
+ mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
+ break;
+ case CK_Swap:
+ swap_cmd ();
+ break;
+ case CK_LinkSymbolic:
+ link_cmd (LINK_SYMLINK_ABSOLUTE);
+ break;
+ case CK_ShowHidden:
+ toggle_show_hidden ();
+ break;
+ case CK_SplitVertHoriz:
+ toggle_panels_split ();
+ break;
+ case CK_SplitEqual:
+ panels_split_equal ();
+ break;
+ case CK_SplitMore:
+ panels_split_more ();
+ break;
+ case CK_SplitLess:
+ panels_split_less ();
+ break;
+ case CK_PanelTree:
+ panel_tree_cmd ();
+ break;
+ case CK_Tree:
+ treebox_cmd ();
+ break;
+#ifdef ENABLE_VFS_UNDELFS
+ case CK_Undelete:
+ undelete_cmd ();
+ break;
+#endif
+ case CK_UserMenu:
+ user_file_menu_cmd ();
+ break;
+ case CK_View:
+ view_cmd (current_panel);
+ break;
+ case CK_ViewFile:
+ view_file_cmd (current_panel);
+ break;
+ case CK_EditorViewerHistory:
+ show_editor_viewer_history ();
+ break;
+ case CK_Cancel:
+ /* don't close panels due to SIGINT */
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Whether the command-line should not respond to key events.
+ *
+ * This is TRUE if a QuickView or TreeView have the focus, as they're going
+ * to consume some keys and there's no sense in passing to the command-line
+ * just the leftovers.
+ */
+static gboolean
+is_cmdline_mute (void)
+{
+ /* When one of panels is other than view_listing,
+ current_panel points to view_listing panel all time independently of
+ it's activity. Thus, we can't use get_current_type() here.
+ current_panel should point to actually current active panel
+ independently of it's type. */
+ return (!current_panel->active
+ && (get_other_type () == view_quick || get_other_type () == view_tree));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handles the Enter key on the command-line.
+ *
+ * Returns TRUE if non-whitespace was indeed processed.
+ */
+static gboolean
+handle_cmdline_enter (void)
+{
+ const char *s;
+
+ for (s = input_get_ctext (cmdline); *s != '\0' && whitespace (*s); s++)
+ ;
+
+ if (*s != '\0')
+ {
+ send_message (cmdline, NULL, MSG_KEY, '\n', NULL);
+ return TRUE;
+ }
+
+ input_insert (cmdline, "", FALSE);
+ cmdline->point = 0;
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+midnight_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ long command;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ panel_init ();
+ setup_panels ();
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ load_hint (TRUE);
+ group_default_callback (w, NULL, MSG_DRAW, 0, NULL);
+ /* We handle the special case of the output lines */
+ if (mc_global.tty.console_flag != '\0' && output_lines != 0)
+ {
+ unsigned char end_line;
+
+ end_line = LINES - (mc_global.keybar_visible ? 1 : 0) - 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ widget_adjust_position (w->pos_flags, &w->rect);
+ setup_panels ();
+ menubar_arrange (the_menubar);
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ /* We only need the first idle event to show user menu after start */
+ widget_idle (w, FALSE);
+
+ if (boot_current_is_left)
+ widget_select (get_panel_widget (0));
+ else
+ widget_select (get_panel_widget (1));
+
+ if (auto_menu)
+ midnight_execute_cmd (NULL, CK_UserMenu);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (w->ext_mode)
+ {
+ command = widget_lookup_key (w, parm);
+ if (command != CK_IgnoreKey)
+ return midnight_execute_cmd (NULL, command);
+ }
+
+ /* FIXME: should handle all menu shortcuts before this point */
+ if (widget_get_state (WIDGET (the_menubar), WST_FOCUSED))
+ return MSG_NOT_HANDLED;
+
+ if (parm == '\n' && !is_cmdline_mute ())
+ {
+ if (handle_cmdline_enter ())
+ return MSG_HANDLED;
+ /* Else: the panel will handle it. */
+ }
+
+ if ((!mc_global.tty.alternate_plus_minus
+ || !(mc_global.tty.console_flag != '\0' || mc_global.tty.xterm_flag)) && !quote
+ && !current_panel->quick_search.active)
+ {
+ if (!only_leading_plus_minus)
+ {
+ /* Special treatment, since the input line will eat them */
+ if (parm == '+')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL);
+
+ if (parm == '\\' || parm == '-')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL);
+
+ if (parm == '*')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert,
+ NULL);
+ }
+ else if (!command_prompt || input_is_empty (cmdline))
+ {
+ /* Special treatment '+', '-', '\', '*' only when this is
+ * first char on input line
+ */
+ if (parm == '+')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Select, NULL);
+
+ if (parm == '\\' || parm == '-')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_Unselect, NULL);
+
+ if (parm == '*')
+ return send_message (current_panel, filemanager, MSG_ACTION, CK_SelectInvert,
+ NULL);
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_HOTKEY_HANDLED:
+ if ((get_current_type () == view_listing) && current_panel->quick_search.active)
+ {
+ current_panel->dirty = TRUE; /* FIXME: unneeded? */
+ send_message (current_panel, NULL, MSG_ACTION, CK_SearchStop, NULL);
+ }
+ return MSG_HANDLED;
+
+ case MSG_UNHANDLED_KEY:
+ {
+ cb_ret_t v = MSG_NOT_HANDLED;
+
+ command = widget_lookup_key (w, parm);
+ if (command != CK_IgnoreKey)
+ v = midnight_execute_cmd (NULL, command);
+
+ if (v == MSG_NOT_HANDLED && command_prompt && !is_cmdline_mute ())
+ v = send_message (cmdline, NULL, MSG_KEY, parm, NULL);
+
+ return v;
+ }
+
+ case MSG_POST_KEY:
+ if (!widget_get_state (WIDGET (the_menubar), WST_FOCUSED))
+ update_dirty_panels ();
+ return MSG_HANDLED;
+
+ case MSG_ACTION:
+ /* Handle shortcuts, menu, and buttonbar. */
+ return midnight_execute_cmd (sender, parm);
+
+ case MSG_DESTROY:
+ panel_deinit ();
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+update_menu (void)
+{
+ menu_set_name (left_menu, panels_layout.horizontal_split ? _("&Above") : _("&Left"));
+ menu_set_name (right_menu, panels_layout.horizontal_split ? _("&Below") : _("&Right"));
+ menubar_arrange (the_menubar);
+ widget_set_visibility (WIDGET (the_menubar), menubar_visible);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+midnight_set_buttonbar (WButtonBar * b)
+{
+ Widget *w = WIDGET (filemanager);
+
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, NULL);
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Menu"), w->keymap, NULL);
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|View"), w->keymap, NULL);
+ buttonbar_set_label (b, 4, Q_ ("ButtonBar|Edit"), w->keymap, NULL);
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, NULL);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, NULL);
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, NULL);
+ buttonbar_set_label (b, 8, Q_ ("ButtonBar|Delete"), w->keymap, NULL);
+ buttonbar_set_label (b, 9, Q_ ("ButtonBar|PullDn"), w->keymap, NULL);
+ buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), w->keymap, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return a random hint. If force is TRUE, ignore the timeout.
+ */
+
+char *
+get_random_hint (gboolean force)
+{
+ static const gint64 update_period = 60 * G_USEC_PER_SEC;
+ static gint64 tv = 0;
+
+ char *data, *result, *eop;
+ size_t len, start;
+ GIConv conv;
+
+ /* Do not change hints more often than one minute */
+ if (!force && !mc_time_elapsed (&tv, update_period))
+ return g_strdup ("");
+
+ data = load_mc_home_file (mc_global.share_data_dir, MC_HINT, NULL, &len);
+ if (data == NULL)
+ return NULL;
+
+ /* get a random entry */
+ srand ((unsigned int) (tv / G_USEC_PER_SEC));
+ start = ((size_t) rand ()) % (len - 1);
+
+ /* Search the start of paragraph */
+ for (; start != 0; start--)
+ if (data[start] == '\n' && data[start + 1] == '\n')
+ {
+ start += 2;
+ break;
+ }
+
+ /* Search the end of paragraph */
+ for (eop = data + start; *eop != '\0'; eop++)
+ {
+ if (*eop == '\n' && *(eop + 1) == '\n')
+ {
+ *eop = '\0';
+ break;
+ }
+ if (*eop == '\n')
+ *eop = ' ';
+ }
+
+ /* hint files are stored in utf-8 */
+ /* try convert hint file from utf-8 to terminal encoding */
+ conv = str_crt_conv_from ("UTF-8");
+ if (conv == INVALID_CONV)
+ result = g_strndup (data + start, len - start);
+ else
+ {
+ GString *buffer;
+ gboolean nok;
+
+ buffer = g_string_sized_new (len - start);
+ nok = (str_convert (conv, data + start, buffer) == ESTR_FAILURE);
+ result = g_string_free (buffer, nok);
+ str_close_conv (conv);
+ }
+
+ g_free (data);
+ return result;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Load new hint and display it.
+ * IF force is not 0, ignore the timeout.
+ */
+
+void
+load_hint (gboolean force)
+{
+ char *hint;
+
+ if (WIDGET (the_hint)->owner == NULL)
+ return;
+
+ if (!mc_global.message_visible)
+ {
+ label_set_text (the_hint, NULL);
+ return;
+ }
+
+ hint = get_random_hint (force);
+
+ if (hint != NULL)
+ {
+ if (*hint != '\0')
+ set_hintbar (hint);
+ g_free (hint);
+ }
+ else
+ {
+ char text[BUF_SMALL];
+
+ g_snprintf (text, sizeof (text), _("GNU Midnight Commander %s\n"), mc_global.mc_version);
+ set_hintbar (text);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change current panel in the file manager.
+ *
+ * @return current_panel
+ */
+
+WPanel *
+change_panel (void)
+{
+ input_complete_free (cmdline);
+ group_select_next_widget (GROUP (filemanager));
+ return current_panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Save current stat of directories to avoid reloading the panels
+ * when no modifications have taken place
+ */
+void
+save_cwds_stat (void)
+{
+ if (panels_options.fast_reload)
+ {
+ mc_stat (current_panel->cwd_vpath, &(current_panel->dir_stat));
+ if (get_other_type () == view_listing)
+ mc_stat (other_panel->cwd_vpath, &(other_panel->dir_stat));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+quiet_quit_cmd (void)
+{
+ print_last_revert = TRUE;
+ return quit_cmd_internal (1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Run the main dialog that occupies the whole screen */
+gboolean
+do_nc (void)
+{
+ gboolean ret;
+
+#ifdef USE_INTERNAL_EDIT
+ edit_stack_init ();
+#endif
+
+ filemanager = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, dialog_colors,
+ midnight_callback, NULL, "[main]", NULL);
+
+ /* Check if we were invoked as an editor or file viewer */
+ if (mc_global.mc_run_mode != MC_RUN_FULL)
+ {
+ setup_dummy_mc ();
+ ret = mc_maybe_editor_or_viewer ();
+ }
+ else
+ {
+ /* We only need the first idle event to show user menu after start */
+ widget_idle (WIDGET (filemanager), TRUE);
+
+ setup_mc ();
+ mc_filehighlight = mc_fhl_new (TRUE);
+
+ create_file_manager ();
+ (void) dlg_run (filemanager);
+
+ mc_fhl_free (&mc_filehighlight);
+
+ ret = TRUE;
+
+ /* widget_destroy destroys even current_panel->cwd_vpath, so we have to save a copy :) */
+ if (mc_args__last_wd_file != NULL && vfs_current_is_local ())
+ last_wd_string = g_strdup (vfs_path_as_str (current_panel->cwd_vpath));
+
+ /* don't handle VFS timestamps for dirs opened in panels */
+ mc_event_destroy (MCEVENT_GROUP_CORE, "vfs_timestamp");
+ }
+
+ /* Program end */
+ mc_global.midnight_shutdown = TRUE;
+ dialog_switch_shutdown ();
+ done_mc ();
+ widget_destroy (WIDGET (filemanager));
+ current_panel = NULL;
+
+#ifdef USE_INTERNAL_EDIT
+ edit_stack_free ();
+#endif
+
+ if ((quit & SUBSHELL_EXIT) == 0)
+ tty_clear_screen ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filemanager.h b/src/filemanager/filemanager.h
new file mode 100644
index 0000000..ca607ce
--- /dev/null
+++ b/src/filemanager/filemanager.h
@@ -0,0 +1,53 @@
+/** \file filemanager.h
+ * \brief Header: main dialog (file panels) for Midnight Commander
+ */
+
+#ifndef MC__FILEMANAGER_H
+#define MC__FILEMANAGER_H
+
+#include "lib/widget.h"
+
+#include "panel.h"
+#include "layout.h"
+
+/* TODO: merge content of layout.h here */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define MENU_PANEL (mc_global.widget.is_right ? right_panel : left_panel)
+#define MENU_PANEL_IDX (mc_global.widget.is_right ? 1 : 0)
+#define SELECTED_IS_PANEL (get_panel_type (MENU_PANEL_IDX) == view_listing)
+
+#define other_panel get_other_panel()
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WMenuBar *the_menubar;
+extern WLabel *the_prompt;
+extern WLabel *the_hint;
+extern WButtonBar *the_bar;
+
+extern WPanel *left_panel;
+extern WPanel *right_panel;
+extern WPanel *current_panel;
+
+extern const char *mc_prompt;
+
+/*** declarations of public functions ************************************************************/
+
+void update_menu (void);
+void midnight_set_buttonbar (WButtonBar * b);
+char *get_random_hint (gboolean force);
+void load_hint (gboolean force);
+WPanel *change_panel (void);
+void save_cwds_stat (void);
+gboolean quiet_quit_cmd (void);
+gboolean do_nc (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILEMANAGER_H */
diff --git a/src/filemanager/filenot.c b/src/filemanager/filenot.c
new file mode 100644
index 0000000..2bfc76a
--- /dev/null
+++ b/src/filemanager/filenot.c
@@ -0,0 +1,150 @@
+/*
+ Wrapper for routines to notify the
+ tree about the changes made to the directory
+ structure.
+
+ Copyright (C) 2011-2023
+ Free Software Foundation, Inc.
+
+ Author:
+ Janne Kukonlehto
+ Miguel de Icaza
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/** \file filenot.c
+ * \brief Source: wrapper for routines to notify the
+ * tree about the changes made to the directory
+ * structure.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <string.h>
+
+#include "lib/global.h"
+#include "lib/fs.h"
+#include "lib/util.h"
+#include "lib/vfs/vfs.h"
+
+#include "filenot.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+get_absolute_name (const vfs_path_t * vpath)
+{
+ if (vpath == NULL)
+ return NULL;
+
+ if (IS_PATH_SEP (*vfs_path_get_by_index (vpath, 0)->path))
+ return vfs_path_clone (vpath);
+
+ return vfs_path_append_vpath_new (vfs_get_raw_current_dir (), vpath, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+my_mkdir_rec (const vfs_path_t * vpath, mode_t mode)
+{
+ vfs_path_t *q;
+ int result;
+
+ if (mc_mkdir (vpath, mode) == 0)
+ return 0;
+ if (errno != ENOENT)
+ return (-1);
+
+ /* FIXME: should check instead if vpath is at the root of that filesystem */
+ if (!vfs_file_is_local (vpath))
+ return (-1);
+
+ if (strcmp (vfs_path_as_str (vpath), PATH_SEP_STR) == 0)
+ {
+ errno = ENOTDIR;
+ return (-1);
+ }
+
+ q = vfs_path_append_new (vpath, "..", (char *) NULL);
+ result = my_mkdir_rec (q, mode);
+ vfs_path_free (q, TRUE);
+
+ if (result == 0)
+ result = mc_mkdir (vpath, mode);
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+my_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ int result;
+
+ result = my_mkdir_rec (vpath, mode);
+ if (result == 0)
+ {
+ vfs_path_t *my_s;
+
+ my_s = get_absolute_name (vpath);
+ vfs_path_free (my_s, TRUE);
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+my_rmdir (const char *path)
+{
+ int result;
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str_flags (path, VPF_NO_CANON);
+ /* FIXME: Should receive a Wtree! */
+ result = mc_rmdir (vpath);
+ if (result == 0)
+ {
+ vfs_path_t *my_s;
+
+ my_s = get_absolute_name (vpath);
+ vfs_path_free (my_s, TRUE);
+ }
+ vfs_path_free (vpath, TRUE);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/filenot.h b/src/filemanager/filenot.h
new file mode 100644
index 0000000..33991e8
--- /dev/null
+++ b/src/filemanager/filenot.h
@@ -0,0 +1,26 @@
+/** \file file.h
+ * \brief Header: File and directory operation routines
+ */
+
+#ifndef MC__FILENOT_H
+#define MC__FILENOT_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Misc Unix functions */
+int my_mkdir (const vfs_path_t * vpath, mode_t mode);
+int my_rmdir (const char *path);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__FILE_H */
diff --git a/src/filemanager/fileopctx.c b/src/filemanager/fileopctx.c
new file mode 100644
index 0000000..a118749
--- /dev/null
+++ b/src/filemanager/fileopctx.c
@@ -0,0 +1,128 @@
+/*
+ File operation contexts for the Midnight Commander
+
+ Copyright (C) 1999-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Federico Mena <federico@nuclecu.unam.mx>
+ Miguel de Icaza <miguel@nuclecu.unam.mx>
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file fileopctx.c
+ * \brief Source: file operation contexts
+ * \date 1998-2007
+ * \author Federico Mena <federico@nuclecu.unam.mx>
+ * \author Miguel de Icaza <miguel@nuclecu.unam.mx>
+ */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "fileopctx.h"
+#include "filegui.h"
+#include "lib/search.h"
+#include "lib/vfs/vfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * \fn file_op_context_t * file_op_context_new (FileOperation op)
+ * \param op file operation struct
+ * \return The newly-created context, filled with the default file mask values.
+ *
+ * Creates a new file operation context with the default values. If you later want
+ * to have a user interface for this, call file_op_context_create_ui().
+ */
+
+file_op_context_t *
+file_op_context_new (FileOperation op)
+{
+ file_op_context_t *ctx;
+
+ ctx = g_new0 (file_op_context_t, 1);
+ ctx->operation = op;
+ ctx->eta_secs = 0.0;
+ ctx->progress_bytes = 0;
+ ctx->op_preserve = TRUE;
+ ctx->do_reget = 1;
+ ctx->stat_func = mc_lstat;
+ ctx->preserve = TRUE;
+ ctx->preserve_uidgid = (geteuid () == 0);
+ ctx->umask_kill = 0777777;
+ ctx->erase_at_end = TRUE;
+ ctx->skip_all = FALSE;
+
+ return ctx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn void file_op_context_destroy (file_op_context_t *ctx)
+ * \param ctx The file operation context to destroy.
+ *
+ * Destroys the specified file operation context and its associated UI data, if
+ * it exists.
+ */
+
+void
+file_op_context_destroy (file_op_context_t * ctx)
+{
+ if (ctx != NULL)
+ {
+ file_op_context_destroy_ui (ctx);
+ mc_search_free (ctx->search_handle);
+ g_free (ctx);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+file_op_total_context_t *
+file_op_total_context_new (void)
+{
+ file_op_total_context_t *tctx;
+ tctx = g_new0 (file_op_total_context_t, 1);
+ tctx->ask_overwrite = TRUE;
+ return tctx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_op_total_context_destroy (file_op_total_context_t * tctx)
+{
+ g_free (tctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/fileopctx.h b/src/filemanager/fileopctx.h
new file mode 100644
index 0000000..ed0b5d6
--- /dev/null
+++ b/src/filemanager/fileopctx.h
@@ -0,0 +1,198 @@
+/** \file fileopctx.h
+ * \brief Header: file operation contexts
+ * \date 1998
+ * \author Federico Mena <federico@nuclecu.unam.mx>
+ * \author Miguel de Icaza <miguel@nuclecu.unam.mx>
+ */
+
+#ifndef MC__FILEOPCTX_H
+#define MC__FILEOPCTX_H
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <inttypes.h> /* uintmax_t */
+
+#include "lib/global.h"
+#include "lib/vfs/vfs.h"
+
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef int (*mc_stat_fn) (const vfs_path_t * vpath, struct stat * buf);
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ FILEGUI_DIALOG_ONE_ITEM,
+ FILEGUI_DIALOG_MULTI_ITEM,
+ FILEGUI_DIALOG_DELETE_ITEM
+} filegui_dialog_type_t;
+
+typedef enum
+{
+ OP_COPY = 0,
+ OP_MOVE = 1,
+ OP_DELETE = 2
+} FileOperation;
+
+typedef enum
+{
+ RECURSIVE_YES = 0,
+ RECURSIVE_NO = 1,
+ RECURSIVE_ALWAYS = 2,
+ RECURSIVE_NEVER = 3,
+ RECURSIVE_ABORT = 4
+} FileCopyMode;
+
+/* ATTENTION: avoid overlapping with B_* values (lib/widget/dialog.h) */
+typedef enum
+{
+ FILE_CONT = 10,
+ FILE_RETRY,
+ FILE_SKIP,
+ FILE_ABORT,
+ FILE_SKIPALL,
+ FILE_SUSPEND
+} FileProgressStatus;
+
+/* First argument passed to real functions */
+enum OperationMode
+{
+ Foreground,
+ Background
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct mc_search_struct;
+
+/* This structure describes a context for file operations. It is used to update
+ * the progress windows and pass around options.
+ */
+typedef struct
+{
+ /* Operation type (copy, move, delete) */
+ FileOperation operation;
+
+ /* The estimated time of arrival in seconds */
+ double eta_secs;
+
+ /* Transferred bytes per second */
+ long bps;
+
+ /* Transferred seconds */
+ long bps_time;
+
+ /* Whether the panel total has been computed */
+ gboolean progress_totals_computed;
+ filegui_dialog_type_t dialog_type;
+
+ /* Counters for progress indicators */
+ size_t progress_count;
+ uintmax_t progress_bytes;
+
+ /* The value of the "preserve Attributes" checkbox in the copy file dialog.
+ * We can't use the value of "ctx->preserve" because it can change in order
+ * to preserve file attributes when moving files across filesystem boundaries
+ * (we want to keep the value of the checkbox between copy operations).
+ */
+ gboolean op_preserve;
+
+ /* Result from the recursive query */
+ FileCopyMode recursive_result;
+
+ /* Whether to do a reget */
+ off_t do_reget;
+
+ /* Controls appending to files */
+ gboolean do_append;
+
+ /* Whether to stat or lstat */
+ gboolean follow_links;
+
+ /* Pointer to the stat function we will use */
+ mc_stat_fn stat_func;
+
+ /* Whether to recompute symlinks */
+ gboolean stable_symlinks;
+
+ /* Preserve the original files' owner, group, permissions, and
+ * timestamps (owner, group only as root).
+ */
+ gboolean preserve;
+
+ /* If running as root, preserve the original uid/gid (we don't want to
+ * try chown for non root) preserve_uidgid = preserve && uid == 0
+ */
+ gboolean preserve_uidgid;
+
+ /* The bits to preserve in created files' modes on file copy */
+ mode_t umask_kill;
+
+ /* The mask of files to actually operate on */
+ char *dest_mask;
+
+ /* search handler */
+ struct mc_search_struct *search_handle;
+
+ /* Whether to dive into subdirectories for recursive operations */
+ gboolean dive_into_subdirs;
+
+ /* When moving directories cross filesystem boundaries delete the
+ * successfully copied files when all files below the directory and its
+ * subdirectories were processed.
+ *
+ * If erase_at_end is FALSE files will be deleted immediately after their
+ * successful copy (Note: this behavior is not tested and at the moment
+ * it can't be changed at runtime).
+ */
+ gboolean erase_at_end;
+
+ /* PID of the child for background operations */
+ pid_t pid;
+
+ /* toggle if all errors should be ignored */
+ gboolean skip_all;
+
+ /* Whether the file operation is in pause */
+ gboolean suspended;
+
+ /* User interface data goes here */
+ void *ui;
+} file_op_context_t;
+
+typedef struct
+{
+ size_t progress_count;
+ size_t prev_progress_count; /* Used in OP_MOVE between copy and remove directories */
+ uintmax_t progress_bytes;
+ uintmax_t copied_bytes;
+ size_t bps;
+ size_t bps_count;
+ gint64 transfer_start;
+ double eta_secs;
+
+ gboolean ask_overwrite;
+} file_op_total_context_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const char *op_names[3];
+
+/*** declarations of public functions ************************************************************/
+
+file_op_context_t *file_op_context_new (FileOperation op);
+void file_op_context_destroy (file_op_context_t * ctx);
+
+file_op_total_context_t *file_op_total_context_new (void);
+void file_op_total_context_destroy (file_op_total_context_t * tctx);
+
+/* The following functions are implemented separately by each port */
+FileProgressStatus file_progress_real_query_replace (file_op_context_t * ctx,
+ enum OperationMode mode, const char *src,
+ struct stat *src_stat, const char *dst,
+ struct stat *dst_stat);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__FILEOPCTX_H */
diff --git a/src/filemanager/find.c b/src/filemanager/find.c
new file mode 100644
index 0000000..c0d2cf9
--- /dev/null
+++ b/src/filemanager/find.c
@@ -0,0 +1,1968 @@
+/*
+ Find file command for the Midnight Commander
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file find.c
+ * \brief Source: Find file command
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/skin.h"
+#include "lib/search.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* canonicalize_pathname() */
+
+#include "src/setup.h" /* verbose */
+#include "src/history.h" /* MC_HISTORY_SHARED_SEARCH */
+
+#include "dir.h"
+#include "cmd.h" /* find_cmd(), view_file_at_line() */
+#include "boxes.h"
+#include "panelize.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MAX_REFRESH_INTERVAL (G_USEC_PER_SEC / 20) /* 50 ms */
+#define MIN_REFRESH_FILE_SIZE (256 * 1024) /* 256 KB */
+
+/*** file scope type declarations ****************************************************************/
+
+/* A couple of extra messages we need */
+enum
+{
+ B_STOP = B_USER + 1,
+ B_AGAIN,
+ B_PANELIZE,
+ B_TREE,
+ B_VIEW
+};
+
+typedef enum
+{
+ FIND_CONT = 0,
+ FIND_SUSPEND,
+ FIND_ABORT
+} FindProgressStatus;
+
+/* find file options */
+typedef struct
+{
+ /* file name options */
+ gboolean file_case_sens;
+ gboolean file_pattern;
+ gboolean find_recurs;
+ gboolean follow_symlinks;
+ gboolean skip_hidden;
+ gboolean file_all_charsets;
+
+ /* file content options */
+ gboolean content_case_sens;
+ gboolean content_regexp;
+ gboolean content_first_hit;
+ gboolean content_whole_words;
+ gboolean content_all_charsets;
+
+ /* whether use ignore dirs or not */
+ gboolean ignore_dirs_enable;
+ /* list of directories to be ignored, separated by ':' */
+ char *ignore_dirs;
+} find_file_options_t;
+
+typedef struct
+{
+ char *dir;
+ gsize start;
+ gsize end;
+} find_match_location_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* button callbacks */
+static int start_stop (WButton * button, int action);
+static int find_do_view_file (WButton * button, int action);
+static int find_do_edit_file (WButton * button, int action);
+
+/*** file scope variables ************************************************************************/
+
+/* Parsed ignore dirs */
+static char **find_ignore_dirs = NULL;
+
+/* static variables to remember find parameters */
+static WInput *in_start; /* Start path */
+static WInput *in_name; /* Filename */
+static WInput *in_with; /* Text */
+static WInput *in_ignore;
+static WLabel *content_label; /* 'Content:' label */
+static WCheck *file_case_sens_cbox; /* "case sensitive" checkbox */
+static WCheck *file_pattern_cbox; /* File name is glob or regexp */
+static WCheck *recursively_cbox;
+static WCheck *follow_sym_cbox;
+static WCheck *skip_hidden_cbox;
+static WCheck *content_case_sens_cbox; /* "case sensitive" checkbox */
+static WCheck *content_regexp_cbox; /* "find regular expression" checkbox */
+static WCheck *content_first_hit_cbox; /* "First hit" checkbox" */
+static WCheck *content_whole_words_cbox; /* "whole words" checkbox */
+#ifdef HAVE_CHARSET
+static WCheck *file_all_charsets_cbox;
+static WCheck *content_all_charsets_cbox;
+#endif
+static WCheck *ignore_dirs_cbox;
+
+static gboolean running = FALSE; /* nice flag */
+static char *find_pattern = NULL; /* Pattern to search */
+static char *content_pattern = NULL; /* pattern to search inside files; if
+ content_regexp_flag is true, it contains the
+ regex pattern, else the search string. */
+static gboolean content_is_empty = TRUE; /* remember content field state; initially is empty */
+static unsigned long matches; /* Number of matches */
+static gboolean is_start = FALSE; /* Status of the start/stop toggle button */
+static char *old_dir = NULL;
+
+static gint64 last_refresh;
+
+/* Where did we stop */
+static gboolean resuming;
+static int last_line;
+static int last_pos;
+static off_t last_off;
+static int last_i;
+
+static size_t ignore_count = 0;
+
+static WDialog *find_dlg; /* The dialog */
+static WLabel *status_label; /* Finished, Searching etc. */
+static WLabel *found_num_label; /* Number of found items */
+
+/* This keeps track of the directory stack */
+static GQueue dir_queue = G_QUEUE_INIT;
+
+/* *INDENT-OFF* */
+static struct
+{
+ int ret_cmd;
+ button_flags_t flags;
+ const char *text;
+ int len; /* length including space and brackets */
+ int x;
+ Widget *button;
+ bcback_fn callback;
+} fbuts[] =
+{
+ { B_ENTER, DEFPUSH_BUTTON, N_("&Chdir"), 0, 0, NULL, NULL },
+ { B_AGAIN, NORMAL_BUTTON, N_("&Again"), 0, 0, NULL, NULL },
+ { B_STOP, NORMAL_BUTTON, N_("S&uspend"), 0, 0, NULL, start_stop },
+ { B_STOP, NORMAL_BUTTON, N_("Con&tinue"), 0, 0, NULL, NULL },
+ { B_CANCEL, NORMAL_BUTTON, N_("&Quit"), 0, 0, NULL, NULL },
+
+ { B_PANELIZE, NORMAL_BUTTON, N_("Pane&lize"), 0, 0, NULL, NULL },
+ { B_VIEW, NORMAL_BUTTON, N_("&View - F3"), 0, 0, NULL, find_do_view_file },
+ { B_VIEW, NORMAL_BUTTON, N_("&Edit - F4"), 0, 0, NULL, find_do_edit_file }
+};
+/* *INDENT-ON* */
+
+static const size_t fbuts_num = G_N_ELEMENTS (fbuts);
+static const size_t quit_button = 4; /* index of "Quit" button */
+
+static WListbox *find_list; /* Listbox with the file list */
+
+static find_file_options_t options = {
+ TRUE, TRUE, TRUE, FALSE, FALSE, FALSE,
+ TRUE, FALSE, FALSE, FALSE, FALSE,
+ FALSE, NULL
+};
+
+static char *in_start_dir = INPUT_LAST_TEXT;
+
+static mc_search_t *search_file_handle = NULL;
+static mc_search_t *search_content_handle = NULL;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* don't use max macro to avoid double str_term_width1() call in widget length calculation */
+#undef max
+
+static int
+max (int a, int b)
+{
+ return (a > b ? a : b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+parse_ignore_dirs (const char *ignore_dirs)
+{
+ size_t r = 0, w = 0; /* read and write iterators */
+
+ if (!options.ignore_dirs_enable || ignore_dirs == NULL || ignore_dirs[0] == '\0')
+ return;
+
+ find_ignore_dirs = g_strsplit (ignore_dirs, ":", -1);
+
+ /* Values like '/foo::/bar: produce holes in list.
+ * Find and remove them */
+ for (; find_ignore_dirs[r] != NULL; r++)
+ {
+ if (find_ignore_dirs[r][0] == '\0')
+ {
+ /* empty entry -- skip it */
+ MC_PTR_FREE (find_ignore_dirs[r]);
+ continue;
+ }
+
+ if (r != w)
+ {
+ /* copy entry to the previous free array cell */
+ find_ignore_dirs[w] = find_ignore_dirs[r];
+ find_ignore_dirs[r] = NULL;
+ }
+
+ canonicalize_pathname (find_ignore_dirs[w]);
+ if (find_ignore_dirs[w][0] != '\0')
+ w++;
+ else
+ MC_PTR_FREE (find_ignore_dirs[w]);
+ }
+
+ if (find_ignore_dirs[0] == NULL)
+ {
+ g_strfreev (find_ignore_dirs);
+ find_ignore_dirs = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_load_options (void)
+{
+ static gboolean loaded = FALSE;
+
+ if (loaded)
+ return;
+
+ loaded = TRUE;
+
+ options.file_case_sens =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_case_sens", TRUE);
+ options.file_pattern =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_shell_pattern", TRUE);
+ options.find_recurs =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_find_recurs", TRUE);
+ options.follow_symlinks =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "follow_symlinks", FALSE);
+ options.skip_hidden =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_skip_hidden", FALSE);
+ options.file_all_charsets =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "file_all_charsets", FALSE);
+ options.content_case_sens =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_case_sens", TRUE);
+ options.content_regexp =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_regexp", FALSE);
+ options.content_first_hit =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_first_hit", FALSE);
+ options.content_whole_words =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_whole_words", FALSE);
+ options.content_all_charsets =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "content_all_charsets", FALSE);
+ options.ignore_dirs_enable =
+ mc_config_get_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable", TRUE);
+ options.ignore_dirs =
+ mc_config_get_string (mc_global.main_config, "FindFile", "ignore_dirs", "");
+
+ if (options.ignore_dirs[0] == '\0')
+ MC_PTR_FREE (options.ignore_dirs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_save_options (void)
+{
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_case_sens",
+ options.file_case_sens);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_shell_pattern",
+ options.file_pattern);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_find_recurs", options.find_recurs);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "follow_symlinks",
+ options.follow_symlinks);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_skip_hidden", options.skip_hidden);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "file_all_charsets",
+ options.file_all_charsets);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_case_sens",
+ options.content_case_sens);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_regexp",
+ options.content_regexp);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_first_hit",
+ options.content_first_hit);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_whole_words",
+ options.content_whole_words);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "content_all_charsets",
+ options.content_all_charsets);
+ mc_config_set_bool (mc_global.main_config, "FindFile", "ignore_dirs_enable",
+ options.ignore_dirs_enable);
+ mc_config_set_string (mc_global.main_config, "FindFile", "ignore_dirs", options.ignore_dirs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+add_to_list (const char *text, void *data)
+{
+ return listbox_add_item (find_list, LISTBOX_APPEND_AT_END, 0, text, data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+stop_idle (void *data)
+{
+ widget_idle (WIDGET (data), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+status_update (const char *text)
+{
+ label_set_text (status_label, text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+found_num_update (void)
+{
+ label_set_textv (found_num_label, _("Found: %lu"), matches);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+get_list_info (char **file, char **dir, gsize * start, gsize * end)
+{
+ find_match_location_t *location;
+
+ listbox_get_current (find_list, file, (void **) &location);
+ if (location != NULL)
+ {
+ if (dir != NULL)
+ *dir = location->dir;
+ if (start != NULL)
+ *start = location->start;
+ if (end != NULL)
+ *end = location->end;
+ }
+ else
+ {
+ if (dir != NULL)
+ *dir = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** check regular expression */
+
+static gboolean
+find_check_regexp (const char *r)
+{
+ mc_search_t *search;
+ gboolean regexp_ok = FALSE;
+
+ search = mc_search_new (r, NULL);
+
+ if (search != NULL)
+ {
+ search->search_type = MC_SEARCH_T_REGEX;
+ regexp_ok = mc_search_prepare (search);
+ mc_search_free (search);
+ }
+
+ return regexp_ok;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_ignore_dirs (void)
+{
+ widget_disable (WIDGET (in_ignore), !ignore_dirs_cbox->state);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_params (void)
+{
+ gboolean disable = input_is_empty (in_name);
+
+ widget_disable (WIDGET (file_pattern_cbox), disable);
+ widget_disable (WIDGET (file_case_sens_cbox), disable);
+#ifdef HAVE_CHARSET
+ widget_disable (WIDGET (file_all_charsets_cbox), disable);
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_toggle_enable_content (void)
+{
+ widget_disable (WIDGET (content_regexp_cbox), content_is_empty);
+ widget_disable (WIDGET (content_case_sens_cbox), content_is_empty);
+#ifdef HAVE_CHARSET
+ widget_disable (WIDGET (content_all_charsets_cbox), content_is_empty);
+#endif
+ widget_disable (WIDGET (content_whole_words_cbox), content_is_empty);
+ widget_disable (WIDGET (content_first_hit_cbox), content_is_empty);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback for the parameter dialog.
+ * Validate regex, prevent closing the dialog if it's invalid.
+ */
+
+static cb_ret_t
+find_parm_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ /* FIXME: HACK: use first draw of dialog to resolve widget state dependencies.
+ * Use this time moment to check input field content. We can't do that in MSG_INIT
+ * because history is not loaded yet.
+ * Probably, we want new MSG_ACTIVATE message as complement to MSG_VALIDATE one. Or
+ * we could name it MSG_POST_INIT.
+ *
+ * In one or two other places we use MSG_IDLE instead of MSG_DRAW for a similar
+ * purpose. We should remember to fix those places too when we introduce the new
+ * message.
+ */
+ static gboolean first_draw = TRUE;
+
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ first_draw = TRUE;
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender == WIDGET (ignore_dirs_cbox))
+ {
+ find_toggle_enable_ignore_dirs ();
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+
+ case MSG_VALIDATE:
+ if (h->ret_value != B_ENTER)
+ return MSG_HANDLED;
+
+ /* check filename regexp */
+ if (!file_pattern_cbox->state && !input_is_empty (in_name)
+ && !find_check_regexp (input_get_ctext (in_name)))
+ {
+ /* Don't stop the dialog */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ widget_select (WIDGET (in_name));
+ return MSG_HANDLED;
+ }
+
+ /* check content regexp */
+ if (content_regexp_cbox->state && !content_is_empty
+ && !find_check_regexp (input_get_ctext (in_with)))
+ {
+ /* Don't stop the dialog */
+ widget_set_state (w, WST_ACTIVE, TRUE);
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ widget_select (WIDGET (in_with));
+ return MSG_HANDLED;
+ }
+
+ return MSG_HANDLED;
+
+ case MSG_POST_KEY:
+ if (GROUP (h)->current->data == in_name)
+ find_toggle_enable_params ();
+ else if (GROUP (h)->current->data == in_with)
+ {
+ content_is_empty = input_is_empty (in_with);
+ find_toggle_enable_content ();
+ }
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ if (first_draw)
+ {
+ find_toggle_enable_ignore_dirs ();
+ find_toggle_enable_params ();
+ find_toggle_enable_content ();
+ }
+
+ first_draw = FALSE;
+ MC_FALLTHROUGH; /* to call MSG_DRAW default handler */
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * find_parameters: gets information from the user
+ *
+ * If the return value is TRUE, then the following holds:
+ *
+ * start_dir, ignore_dirs, pattern and content contain the information provided by the user.
+ * They are newly allocated strings and must be freed when unneeded.
+ *
+ * start_dir_len is -1 when user entered an absolute path, otherwise it is a length
+ * of start_dir (which is absolute). It is used to get a relative pats of find results.
+ */
+
+static gboolean
+find_parameters (WPanel * panel, char **start_dir, ssize_t * start_dir_len,
+ char **ignore_dirs, char **pattern, char **content)
+{
+ WGroup *g;
+
+ /* Size of the find parameters window */
+#ifdef HAVE_CHARSET
+ const int lines = 19;
+#else
+ const int lines = 18;
+#endif
+ int cols = 68;
+
+ gboolean return_value;
+
+ /* file name */
+ const char *file_name_label = N_("File name:");
+ const char *file_recurs_label = N_("&Find recursively");
+ const char *file_follow_symlinks = N_("Follow s&ymlinks");
+ const char *file_pattern_label = N_("&Using shell patterns");
+#ifdef HAVE_CHARSET
+ const char *file_all_charsets_label = N_("&All charsets");
+#endif
+ const char *file_case_label = N_("Cas&e sensitive");
+ const char *file_skip_hidden_label = N_("S&kip hidden");
+
+ /* file content */
+ const char *content_content_label = N_("Content:");
+ const char *content_use_label = N_("Sea&rch for content");
+ const char *content_regexp_label = N_("Re&gular expression");
+ const char *content_case_label = N_("Case sens&itive");
+#ifdef HAVE_CHARSET
+ const char *content_all_charsets_label = N_("A&ll charsets");
+#endif
+ const char *content_whole_words_label = N_("&Whole words");
+ const char *content_first_hit_label = N_("Fir&st hit");
+
+ const char *buts[] = { N_("&Tree"), N_("&OK"), N_("&Cancel") };
+
+ /* button lengths */
+ int b0, b1, b2, b12;
+ int y1, y2, x1, x2;
+ /* column width */
+ int cw;
+
+#ifdef ENABLE_NLS
+ {
+ size_t i;
+
+ file_name_label = _(file_name_label);
+ file_recurs_label = _(file_recurs_label);
+ file_follow_symlinks = _(file_follow_symlinks);
+ file_pattern_label = _(file_pattern_label);
+#ifdef HAVE_CHARSET
+ file_all_charsets_label = _(file_all_charsets_label);
+#endif
+ file_case_label = _(file_case_label);
+ file_skip_hidden_label = _(file_skip_hidden_label);
+
+ /* file content */
+ content_content_label = _(content_content_label);
+ content_use_label = _(content_use_label);
+ content_regexp_label = _(content_regexp_label);
+ content_case_label = _(content_case_label);
+#ifdef HAVE_CHARSET
+ content_all_charsets_label = _(content_all_charsets_label);
+#endif
+ content_whole_words_label = _(content_whole_words_label);
+ content_first_hit_label = _(content_first_hit_label);
+
+ for (i = 0; i < G_N_ELEMENTS (buts); i++)
+ buts[i] = _(buts[i]);
+ }
+#endif /* ENABLE_NLS */
+
+ /* calculate dialog width */
+
+ /* widget widths */
+ cw = str_term_width1 (file_name_label);
+ cw = max (cw, str_term_width1 (file_recurs_label) + 4);
+ cw = max (cw, str_term_width1 (file_follow_symlinks) + 4);
+ cw = max (cw, str_term_width1 (file_pattern_label) + 4);
+#ifdef HAVE_CHARSET
+ cw = max (cw, str_term_width1 (file_all_charsets_label) + 4);
+#endif
+ cw = max (cw, str_term_width1 (file_case_label) + 4);
+ cw = max (cw, str_term_width1 (file_skip_hidden_label) + 4);
+
+ cw = max (cw, str_term_width1 (content_content_label) + 4);
+ cw = max (cw, str_term_width1 (content_use_label) + 4);
+ cw = max (cw, str_term_width1 (content_regexp_label) + 4);
+ cw = max (cw, str_term_width1 (content_case_label) + 4);
+#ifdef HAVE_CHARSET
+ cw = max (cw, str_term_width1 (content_all_charsets_label) + 4);
+#endif
+ cw = max (cw, str_term_width1 (content_whole_words_label) + 4);
+ cw = max (cw, str_term_width1 (content_first_hit_label) + 4);
+
+ /* button width */
+ b0 = str_term_width1 (buts[0]) + 3;
+ b1 = str_term_width1 (buts[1]) + 5; /* default button */
+ b2 = str_term_width1 (buts[2]) + 3;
+ b12 = b1 + b2 + 1;
+
+ cols = max (cols, max (b12, cw * 2 + 1) + 6);
+
+ find_load_options ();
+
+ if (in_start_dir == NULL)
+ in_start_dir = g_strdup (".");
+
+ find_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_parm_callback,
+ NULL, "[Find File]", _("Find File"));
+ g = GROUP (find_dlg);
+
+ x1 = 3;
+ x2 = cols / 2 + 1;
+ cw = (cols - 7) / 2;
+ y1 = 2;
+
+ group_add_widget (g, label_new (y1++, x1, _("Start at:")));
+ in_start =
+ input_new (y1, x1, input_colors, cols - b0 - 7, in_start_dir, "start",
+ INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
+ group_add_widget (g, in_start);
+
+ group_add_widget (g, button_new (y1++, cols - b0 - 3, B_TREE, NORMAL_BUTTON, buts[0], NULL));
+
+ ignore_dirs_cbox =
+ check_new (y1++, x1, options.ignore_dirs_enable, _("Ena&ble ignore directories:"));
+ group_add_widget (g, ignore_dirs_cbox);
+
+ in_ignore =
+ input_new (y1++, x1, input_colors, cols - 6,
+ options.ignore_dirs != NULL ? options.ignore_dirs : "", "ignoredirs",
+ INPUT_COMPLETE_CD | INPUT_COMPLETE_FILENAMES);
+ group_add_widget (g, in_ignore);
+
+ group_add_widget (g, hline_new (y1++, -1, -1));
+
+ y2 = y1;
+
+ /* Start 1st column */
+ group_add_widget (g, label_new (y1++, x1, file_name_label));
+ in_name =
+ input_new (y1++, x1, input_colors, cw, INPUT_LAST_TEXT, "name",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+ group_add_widget (g, in_name);
+
+ /* Start 2nd column */
+ content_label = label_new (y2++, x2, content_content_label);
+ group_add_widget (g, content_label);
+ in_with =
+ input_new (y2++, x2, input_colors, cw, content_is_empty ? "" : INPUT_LAST_TEXT,
+ MC_HISTORY_SHARED_SEARCH, INPUT_COMPLETE_NONE);
+ in_with->label = content_label;
+ group_add_widget (g, in_with);
+
+ /* Continue 1st column */
+ recursively_cbox = check_new (y1++, x1, options.find_recurs, file_recurs_label);
+ group_add_widget (g, recursively_cbox);
+
+ follow_sym_cbox = check_new (y1++, x1, options.follow_symlinks, file_follow_symlinks);
+ group_add_widget (g, follow_sym_cbox);
+
+ file_pattern_cbox = check_new (y1++, x1, options.file_pattern, file_pattern_label);
+ group_add_widget (g, file_pattern_cbox);
+
+ file_case_sens_cbox = check_new (y1++, x1, options.file_case_sens, file_case_label);
+ group_add_widget (g, file_case_sens_cbox);
+
+#ifdef HAVE_CHARSET
+ file_all_charsets_cbox =
+ check_new (y1++, x1, options.file_all_charsets, file_all_charsets_label);
+ group_add_widget (g, file_all_charsets_cbox);
+#endif
+
+ skip_hidden_cbox = check_new (y1++, x1, options.skip_hidden, file_skip_hidden_label);
+ group_add_widget (g, skip_hidden_cbox);
+
+ /* Continue 2nd column */
+ content_whole_words_cbox =
+ check_new (y2++, x2, options.content_whole_words, content_whole_words_label);
+ group_add_widget (g, content_whole_words_cbox);
+
+ content_regexp_cbox = check_new (y2++, x2, options.content_regexp, content_regexp_label);
+ group_add_widget (g, content_regexp_cbox);
+
+ content_case_sens_cbox = check_new (y2++, x2, options.content_case_sens, content_case_label);
+ group_add_widget (g, content_case_sens_cbox);
+
+#ifdef HAVE_CHARSET
+ content_all_charsets_cbox =
+ check_new (y2++, x2, options.content_all_charsets, content_all_charsets_label);
+ group_add_widget (g, content_all_charsets_cbox);
+#endif
+
+ content_first_hit_cbox =
+ check_new (y2++, x2, options.content_first_hit, content_first_hit_label);
+ group_add_widget (g, content_first_hit_cbox);
+
+ /* buttons */
+ y1 = max (y1, y2);
+ x1 = (cols - b12) / 2;
+ group_add_widget (g, hline_new (y1++, -1, -1));
+ group_add_widget (g, button_new (y1, x1, B_ENTER, DEFPUSH_BUTTON, buts[1], NULL));
+ group_add_widget (g, button_new (y1, x1 + b1 + 1, B_CANCEL, NORMAL_BUTTON, buts[2], NULL));
+
+ find_par_start:
+ widget_select (WIDGET (in_name));
+
+ switch (dlg_run (find_dlg))
+ {
+ case B_CANCEL:
+ return_value = FALSE;
+ break;
+
+ case B_TREE:
+ {
+ const char *start_cstr;
+ const char *temp_dir;
+
+ start_cstr = input_get_ctext (in_start);
+
+ if (input_is_empty (in_start) || DIR_IS_DOT (start_cstr))
+ temp_dir = vfs_path_as_str (panel->cwd_vpath);
+ else
+ temp_dir = start_cstr;
+
+ if (in_start_dir != INPUT_LAST_TEXT)
+ g_free (in_start_dir);
+ in_start_dir = tree_box (temp_dir);
+ if (in_start_dir == NULL)
+ in_start_dir = g_strdup (temp_dir);
+
+ input_assign_text (in_start, in_start_dir);
+
+ /* Warning: Dreadful goto */
+ goto find_par_start;
+ }
+
+ default:
+ {
+ char *s;
+
+#ifdef HAVE_CHARSET
+ options.file_all_charsets = file_all_charsets_cbox->state;
+ options.content_all_charsets = content_all_charsets_cbox->state;
+#endif
+ options.content_case_sens = content_case_sens_cbox->state;
+ options.content_regexp = content_regexp_cbox->state;
+ options.content_first_hit = content_first_hit_cbox->state;
+ options.content_whole_words = content_whole_words_cbox->state;
+ options.find_recurs = recursively_cbox->state;
+ options.follow_symlinks = follow_sym_cbox->state;
+ options.file_pattern = file_pattern_cbox->state;
+ options.file_case_sens = file_case_sens_cbox->state;
+ options.skip_hidden = skip_hidden_cbox->state;
+ options.ignore_dirs_enable = ignore_dirs_cbox->state;
+ g_free (options.ignore_dirs);
+ options.ignore_dirs = input_get_text (in_ignore);
+
+ *content = !input_is_empty (in_with) ? input_get_text (in_with) : NULL;
+ if (input_is_empty (in_name))
+ *pattern = g_strdup (options.file_pattern ? "*" : ".*");
+ else
+ *pattern = input_get_text (in_name);
+ *start_dir = (char *) (!input_is_empty (in_start) ? input_get_ctext (in_start) : ".");
+ if (in_start_dir != INPUT_LAST_TEXT)
+ g_free (in_start_dir);
+ in_start_dir = g_strdup (*start_dir);
+
+ s = tilde_expand (*start_dir);
+ canonicalize_pathname (s);
+
+ if (DIR_IS_DOT (s))
+ {
+ *start_dir = g_strdup (vfs_path_as_str (panel->cwd_vpath));
+ /* FIXME: is panel->cwd_vpath canonicalized? */
+ /* relative paths will be used in panelization */
+ *start_dir_len = (ssize_t) strlen (*start_dir);
+ g_free (s);
+ }
+ else if (g_path_is_absolute (s))
+ {
+ *start_dir = s;
+ *start_dir_len = -1;
+ }
+ else
+ {
+ /* relative paths will be used in panelization */
+ *start_dir =
+ mc_build_filename (vfs_path_as_str (panel->cwd_vpath), s, (char *) NULL);
+ *start_dir_len = (ssize_t) vfs_path_len (panel->cwd_vpath);
+ g_free (s);
+ }
+
+ if (!options.ignore_dirs_enable || input_is_empty (in_ignore)
+ || DIR_IS_DOT (input_get_ctext (in_ignore)))
+ *ignore_dirs = NULL;
+ else
+ *ignore_dirs = input_get_text (in_ignore);
+
+ find_save_options ();
+
+ return_value = TRUE;
+ }
+ }
+
+ widget_destroy (WIDGET (find_dlg));
+
+ return return_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+push_directory (vfs_path_t * dir)
+{
+ g_queue_push_head (&dir_queue, (void *) dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline vfs_path_t *
+pop_directory (void)
+{
+ return (vfs_path_t *) g_queue_pop_head (&dir_queue);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+queue_dir_free (gpointer data)
+{
+ vfs_path_free ((vfs_path_t *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Remove all the items from the stack */
+
+static void
+clear_stack (void)
+{
+ g_queue_clear_full (&dir_queue, queue_dir_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+insert_file (const char *dir, const char *file, gsize start, gsize end)
+{
+ char *tmp_name = NULL;
+ static char *dirname = NULL;
+ find_match_location_t *location;
+
+ while (IS_PATH_SEP (dir[0]) && IS_PATH_SEP (dir[1]))
+ dir++;
+
+ if (old_dir != NULL)
+ {
+ if (strcmp (old_dir, dir) != 0)
+ {
+ g_free (old_dir);
+ old_dir = g_strdup (dir);
+ dirname = add_to_list (dir, NULL);
+ }
+ }
+ else
+ {
+ old_dir = g_strdup (dir);
+ dirname = add_to_list (dir, NULL);
+ }
+
+ tmp_name = g_strdup_printf (" %s", file);
+ location = g_malloc (sizeof (*location));
+ location->dir = dirname;
+ location->start = start;
+ location->end = end;
+ add_to_list (tmp_name, location);
+ g_free (tmp_name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_add_match (const char *dir, const char *file, gsize start, gsize end)
+{
+ insert_file (dir, file, start, end);
+
+ /* Don't scroll */
+ if (matches == 0)
+ listbox_select_first (find_list);
+ widget_draw (WIDGET (find_list));
+
+ matches++;
+ found_num_update ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static FindProgressStatus
+check_find_events (WDialog * h)
+{
+ Gpm_Event event;
+ int c;
+
+ event.x = -1;
+ c = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, FALSE);
+ if (c != EV_NONE)
+ {
+ dlg_process_event (h, c, &event);
+ if (h->ret_value == B_ENTER
+ || h->ret_value == B_CANCEL || h->ret_value == B_AGAIN || h->ret_value == B_PANELIZE)
+ {
+ /* dialog terminated */
+ return FIND_ABORT;
+ }
+ if (!widget_get_state (WIDGET (h), WST_IDLE))
+ {
+ /* searching suspended */
+ return FIND_SUSPEND;
+ }
+ }
+
+ return FIND_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * search_content:
+ *
+ * Search the content_pattern string in the DIRECTORY/FILE.
+ * It will add the found entries to the find listbox.
+ *
+ * returns FALSE if do_search should look for another file
+ * TRUE if do_search should exit and proceed to the event handler
+ */
+
+static gboolean
+search_content (WDialog * h, const char *directory, const char *filename)
+{
+ struct stat s;
+ char buffer[BUF_4K] = ""; /* raw input buffer */
+ int file_fd;
+ gboolean ret_val = FALSE;
+ vfs_path_t *vpath;
+ gint64 tv;
+ gboolean status_updated = FALSE;
+
+ vpath = vfs_path_build_filename (directory, filename, (char *) NULL);
+
+ if (mc_stat (vpath, &s) != 0 || !S_ISREG (s.st_mode))
+ {
+ vfs_path_free (vpath, TRUE);
+ return FALSE;
+ }
+
+ file_fd = mc_open (vpath, O_RDONLY);
+ vfs_path_free (vpath, TRUE);
+
+ if (file_fd == -1)
+ return FALSE;
+
+ /* get time elapsed from last refresh */
+ tv = g_get_monotonic_time ();
+
+ if (s.st_size >= MIN_REFRESH_FILE_SIZE || (tv - last_refresh) > MAX_REFRESH_INTERVAL)
+ {
+ g_snprintf (buffer, sizeof (buffer), _("Grepping in %s"), filename);
+ status_update (str_trunc (buffer, WIDGET (h)->rect.cols - 8));
+ mc_refresh ();
+ last_refresh = tv;
+ status_updated = TRUE;
+ }
+
+ tty_enable_interrupt_key ();
+ tty_got_interrupt ();
+
+ {
+ int line = 1;
+ int pos = 0;
+ int n_read = 0;
+ off_t off = 0; /* file_fd's offset corresponding to strbuf[0] */
+ gboolean found = FALSE;
+ gsize found_len;
+ gsize found_start;
+ char result[BUF_MEDIUM];
+ char *strbuf = NULL; /* buffer for fetched string */
+ int strbuf_size = 0;
+ int i = -1; /* compensate for a newline we'll add when we first enter the loop */
+
+ if (resuming)
+ {
+ /* We've been previously suspended, start from the previous position */
+ resuming = FALSE;
+ line = last_line;
+ pos = last_pos;
+ off = last_off;
+ i = last_i;
+ }
+
+ while (!ret_val)
+ {
+ char ch = '\0';
+
+ off += i + 1; /* the previous line, plus a newline character */
+ i = 0;
+
+ /* read to buffer and get line from there */
+ while (TRUE)
+ {
+ if (pos >= n_read)
+ {
+ pos = 0;
+ n_read = mc_read (file_fd, buffer, sizeof (buffer));
+ if (n_read <= 0)
+ break;
+ }
+
+ ch = buffer[pos++];
+ if (ch == '\0')
+ {
+ /* skip possible leading zero(s) */
+ if (i == 0)
+ {
+ off++;
+ continue;
+ }
+ break;
+ }
+
+ if (i >= strbuf_size - 1)
+ {
+ strbuf_size += 128;
+ strbuf = g_realloc (strbuf, strbuf_size);
+ }
+
+ /* Strip newline */
+ if (ch == '\n')
+ break;
+
+ strbuf[i++] = ch;
+ }
+
+ if (i == 0)
+ {
+ if (ch == '\0')
+ break;
+
+ /* if (ch == '\n'): do not search in empty strings */
+ goto skip_search;
+ }
+
+ strbuf[i] = '\0';
+
+ if (!found /* Search in binary line once */
+ && mc_search_run (search_content_handle, (const void *) strbuf, 0, i, &found_len))
+ {
+ if (!status_updated)
+ {
+ /* if we add results for a file, we have to ensure that
+ name of this file is shown in status bar */
+ g_snprintf (result, sizeof (result), _("Grepping in %s"), filename);
+ status_update (str_trunc (result, WIDGET (h)->rect.cols - 8));
+ mc_refresh ();
+ last_refresh = tv;
+ status_updated = TRUE;
+ }
+
+ g_snprintf (result, sizeof (result), "%d:%s", line, filename);
+ found_start = off + search_content_handle->normal_offset + 1; /* off by one: ticket 3280 */
+ find_add_match (directory, result, found_start, found_start + found_len);
+ found = TRUE;
+ }
+
+ if (found && options.content_first_hit)
+ break;
+
+ if (ch == '\n')
+ {
+ skip_search:
+ found = FALSE;
+ line++;
+ }
+
+ if ((line & 0xff) == 0)
+ {
+ FindProgressStatus res;
+
+ res = check_find_events (h);
+ switch (res)
+ {
+ case FIND_ABORT:
+ stop_idle (h);
+ ret_val = TRUE;
+ break;
+ case FIND_SUSPEND:
+ resuming = TRUE;
+ last_line = line;
+ last_pos = pos;
+ last_off = off;
+ last_i = i;
+ ret_val = TRUE;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ g_free (strbuf);
+ }
+
+ tty_disable_interrupt_key ();
+ mc_close (file_fd);
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ If dir is absolute, this means we're within dir and searching file here.
+ If dir is relative, this means we're going to add dir to the directory stack.
+**/
+static gboolean
+find_ignore_dir_search (const char *dir)
+{
+ if (find_ignore_dirs != NULL)
+ {
+ const size_t dlen = strlen (dir);
+ const unsigned char dabs = g_path_is_absolute (dir) ? 1 : 0;
+
+ char **ignore_dir;
+
+ for (ignore_dir = find_ignore_dirs; *ignore_dir != NULL; ignore_dir++)
+ {
+ const size_t ilen = strlen (*ignore_dir);
+ const unsigned char iabs = g_path_is_absolute (*ignore_dir) ? 2 : 0;
+
+ /* ignore dir is too long -- skip it */
+ if (dlen < ilen)
+ continue;
+
+ /* handle absolute and relative paths */
+ switch (iabs | dabs)
+ {
+ case 0: /* both paths are relative */
+ case 3: /* both paths are absolute */
+ /* if ignore dir is not a path of dir -- skip it */
+ if (strncmp (dir, *ignore_dir, ilen) == 0)
+ {
+ /* be sure that ignore dir is not a part of dir like:
+ ignore dir is "h", dir is "home" */
+ if (dir[ilen] == '\0' || IS_PATH_SEP (dir[ilen]))
+ return TRUE;
+ }
+ break;
+ case 1: /* dir is absolute, ignore_dir is relative */
+ {
+ char *d;
+
+ d = strstr (dir, *ignore_dir);
+ if (d != NULL && IS_PATH_SEP (d[-1])
+ && (d[ilen] == '\0' || IS_PATH_SEP (d[ilen])))
+ return TRUE;
+ }
+ break;
+ case 2: /* dir is relative, ignore_dir is absolute */
+ /* FIXME: skip this case */
+ break;
+ default: /* this cannot occurs */
+ return FALSE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_rotate_dash (const WDialog * h, gboolean show)
+{
+ static size_t pos = 0;
+ static const char rotating_dash[4] = "|/-\\";
+ const Widget *w = CONST_WIDGET (h);
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ widget_gotoyx (h, w->rect.lines - 7, w->rect.cols - 4);
+ tty_print_char (show ? rotating_dash[pos] : ' ');
+ pos = (pos + 1) % sizeof (rotating_dash);
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+do_search (WDialog * h)
+{
+ static struct vfs_dirent *dp = NULL;
+ static DIR *dirp = NULL;
+ static char *directory = NULL;
+ static gboolean pop_start_dir = TRUE;
+ struct stat tmp_stat;
+ gsize bytes_found;
+ unsigned short count;
+
+ if (h == NULL)
+ { /* someone forces me to close dirp */
+ if (dirp != NULL)
+ {
+ mc_closedir (dirp);
+ dirp = NULL;
+ }
+ MC_PTR_FREE (directory);
+ dp = NULL;
+ pop_start_dir = TRUE;
+ return 1;
+ }
+
+ for (count = 0; count < 32; count++)
+ {
+ while (dp == NULL)
+ {
+ if (dirp != NULL)
+ {
+ mc_closedir (dirp);
+ dirp = NULL;
+ }
+
+ while (dirp == NULL)
+ {
+ vfs_path_t *tmp_vpath = NULL;
+
+ tty_setcolor (REVERSE_COLOR);
+
+ while (TRUE)
+ {
+ tmp_vpath = pop_directory ();
+ if (tmp_vpath == NULL)
+ {
+ running = FALSE;
+ if (ignore_count == 0)
+ status_update (_("Finished"));
+ else
+ {
+ char msg[BUF_SMALL];
+
+ g_snprintf (msg, sizeof (msg),
+ ngettext ("Finished (ignored %zu directory)",
+ "Finished (ignored %zu directories)",
+ ignore_count), ignore_count);
+ status_update (msg);
+ }
+ if (verbose)
+ find_rotate_dash (h, FALSE);
+ stop_idle (h);
+ return 0;
+ }
+
+ /* The start directory is the first one in the stack (see do_find() below).
+ Do not apply ignore_dir to it. */
+ if (pop_start_dir)
+ {
+ pop_start_dir = FALSE;
+ break;
+ }
+
+ pop_start_dir = FALSE;
+
+ /* handle absolute ignore dirs here */
+ if (!find_ignore_dir_search (vfs_path_as_str (tmp_vpath)))
+ break;
+
+ vfs_path_free (tmp_vpath, TRUE);
+ ignore_count++;
+ }
+
+ g_free (directory);
+
+ if (verbose)
+ {
+ char buffer[BUF_MEDIUM];
+
+ directory = (char *) vfs_path_as_str (tmp_vpath);
+ g_snprintf (buffer, sizeof (buffer), _("Searching %s"), directory);
+ status_update (str_trunc (directory, WIDGET (h)->rect.cols - 8));
+ }
+
+ dirp = mc_opendir (tmp_vpath);
+ directory = vfs_path_free (tmp_vpath, FALSE);
+ } /* while (!dirp) */
+
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+ } /* while (!dp) */
+
+ if (DIR_IS_DOT (dp->d_name) || DIR_IS_DOTDOT (dp->d_name))
+ {
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+
+ return 1;
+ }
+
+ if (!(options.skip_hidden && (dp->d_name[0] == '.')))
+ {
+ gboolean search_ok;
+
+ if (options.find_recurs && (directory != NULL))
+ { /* Can directory be NULL ? */
+ /* handle relative ignore dirs here */
+ if (options.ignore_dirs_enable && find_ignore_dir_search (dp->d_name))
+ ignore_count++;
+ else
+ {
+ vfs_path_t *tmp_vpath;
+ int stat_res;
+
+ tmp_vpath = vfs_path_build_filename (directory, dp->d_name, (char *) NULL);
+
+ if (options.follow_symlinks)
+ stat_res = mc_stat (tmp_vpath, &tmp_stat);
+ else
+ stat_res = mc_lstat (tmp_vpath, &tmp_stat);
+
+ if (stat_res == 0 && S_ISDIR (tmp_stat.st_mode))
+ push_directory (tmp_vpath);
+ else
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ }
+
+ search_ok = mc_search_run (search_file_handle, dp->d_name,
+ 0, strlen (dp->d_name), &bytes_found);
+
+ if (search_ok)
+ {
+ if (content_pattern == NULL)
+ find_add_match (directory, dp->d_name, 0, 0);
+ else if (search_content (h, directory, dp->d_name))
+ return 1;
+ }
+ }
+
+ /* skip invalid filenames */
+ while ((dp = mc_readdir (dirp)) != NULL && !str_is_valid_string (dp->d_name))
+ ;
+ } /* for */
+
+ if (verbose)
+ find_rotate_dash (h, TRUE);
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_find_vars (void)
+{
+ MC_PTR_FREE (old_dir);
+ matches = 0;
+ ignore_count = 0;
+
+ /* Remove all the items from the stack */
+ clear_stack ();
+
+ g_strfreev (find_ignore_dirs);
+ find_ignore_dirs = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_do_view_edit (gboolean unparsed_view, gboolean edit, char *dir, char *file, off_t search_start,
+ off_t search_end)
+{
+ const char *filename = NULL;
+ int line;
+ vfs_path_t *fullname_vpath;
+
+ if (content_pattern != NULL)
+ {
+ filename = strchr (file + 4, ':') + 1;
+ line = atoi (file + 4);
+ }
+ else
+ {
+ filename = file + 4;
+ line = 0;
+ }
+
+ fullname_vpath = vfs_path_build_filename (dir, filename, (char *) NULL);
+ if (edit)
+ edit_file_at_line (fullname_vpath, use_internal_edit, line);
+ else
+ view_file_at_line (fullname_vpath, unparsed_view, use_internal_view, line, search_start,
+ search_end);
+ vfs_path_free (fullname_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+view_edit_currently_selected_file (gboolean unparsed_view, gboolean edit)
+{
+ char *text = NULL;
+ find_match_location_t *location;
+
+ listbox_get_current (find_list, &text, (void **) &location);
+
+ if ((text == NULL) || (location == NULL) || (location->dir == NULL))
+ return MSG_NOT_HANDLED;
+
+ find_do_view_edit (unparsed_view, edit, location->dir, text, location->start, location->end);
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_calc_button_locations (const WDialog * h, gboolean all_buttons)
+{
+ const int cols = CONST_WIDGET (h)->rect.cols;
+
+ int l1, l2;
+
+ l1 = fbuts[0].len + fbuts[1].len + fbuts[is_start ? 3 : 2].len + fbuts[4].len + 3;
+ l2 = fbuts[5].len + fbuts[6].len + fbuts[7].len + 2;
+
+ fbuts[0].x = (cols - l1) / 2;
+ fbuts[1].x = fbuts[0].x + fbuts[0].len + 1;
+ fbuts[2].x = fbuts[1].x + fbuts[1].len + 1;
+ fbuts[3].x = fbuts[2].x;
+ fbuts[4].x = fbuts[2].x + fbuts[is_start ? 3 : 2].len + 1;
+
+ if (all_buttons)
+ {
+ fbuts[5].x = (cols - l2) / 2;
+ fbuts[6].x = fbuts[5].x + fbuts[5].len + 1;
+ fbuts[7].x = fbuts[6].x + fbuts[6].len + 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_adjust_header (WDialog * h)
+{
+ char title[BUF_MEDIUM];
+ int title_len;
+
+ if (content_pattern != NULL)
+ g_snprintf (title, sizeof (title), _("Find File: \"%s\". Content: \"%s\""), find_pattern,
+ content_pattern);
+ else
+ g_snprintf (title, sizeof (title), _("Find File: \"%s\""), find_pattern);
+
+ title_len = str_term_width1 (title);
+ if (title_len > WIDGET (h)->rect.cols - 6)
+ {
+ /* title is too wide, truncate it */
+ title_len = WIDGET (h)->rect.cols - 6;
+ title_len = str_column_to_pos (title, title_len);
+ title_len -= 3; /* reserve space for three dots */
+ title_len = str_offset_to_pos (title, title_len);
+ /* mark that title is truncated */
+ memmove (title + title_len, "...", 4);
+ }
+
+ frame_set_title (FRAME (h->bg), title);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+find_relocate_buttons (const WDialog * h, gboolean all_buttons)
+{
+ size_t i;
+
+ find_calc_button_locations (h, all_buttons);
+
+ for (i = 0; i < fbuts_num; i++)
+ fbuts[i].button->rect.x = CONST_WIDGET (h)->rect.x + fbuts[i].x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+find_resize (WDialog * h)
+{
+ Widget *w = WIDGET (h);
+ WRect r = w->rect;
+
+ r.lines = LINES - 4;
+ r.cols = COLS - 16;
+ dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ find_adjust_header (h);
+ find_relocate_buttons (h, TRUE);
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+find_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ find_adjust_header (h);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (parm == KEY_F (3) || parm == KEY_F (13))
+ {
+ gboolean unparsed_view = (parm == KEY_F (13));
+
+ return view_edit_currently_selected_file (unparsed_view, FALSE);
+ }
+ if (parm == KEY_F (4))
+ return view_edit_currently_selected_file (FALSE, TRUE);
+ return MSG_NOT_HANDLED;
+
+ case MSG_RESIZE:
+ return find_resize (h);
+
+ case MSG_IDLE:
+ do_search (h);
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handles the Stop/Start button in the find window */
+
+static int
+start_stop (WButton * button, int action)
+{
+ Widget *w = WIDGET (button);
+
+ (void) action;
+
+ running = is_start;
+ widget_idle (WIDGET (find_dlg), running);
+ is_start = !is_start;
+
+ status_update (is_start ? _("Stopped") : _("Searching"));
+ button_set_text (button, fbuts[is_start ? 3 : 2].text);
+
+ find_relocate_buttons (DIALOG (w->owner), FALSE);
+ widget_draw (WIDGET (w->owner));
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handle view command, when invoked as a button */
+
+static int
+find_do_view_file (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ view_edit_currently_selected_file (FALSE, FALSE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Handle edit command, when invoked as a button */
+
+static int
+find_do_edit_file (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ view_edit_currently_selected_file (FALSE, TRUE);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+setup_gui (void)
+{
+ WGroup *g;
+ size_t i;
+ int lines, cols;
+ int y;
+
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ for (i = 0; i < fbuts_num; i++)
+ {
+#ifdef ENABLE_NLS
+ fbuts[i].text = _(fbuts[i].text);
+#endif /* ENABLE_NLS */
+ fbuts[i].len = str_term_width1 (fbuts[i].text) + 3;
+ if (fbuts[i].flags == DEFPUSH_BUTTON)
+ fbuts[i].len += 2;
+ }
+
+ i18n_flag = TRUE;
+ }
+
+ lines = LINES - 4;
+ cols = COLS - 16;
+
+ find_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, find_callback, NULL,
+ "[Find File]", NULL);
+ g = GROUP (find_dlg);
+
+ find_calc_button_locations (find_dlg, TRUE);
+
+ y = 2;
+ find_list = listbox_new (y, 2, lines - 10, cols - 4, FALSE, NULL);
+ group_add_widget_autopos (g, find_list, WPOS_KEEP_ALL, NULL);
+ y += WIDGET (find_list)->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ found_num_label = label_new (y++, 4, NULL);
+ group_add_widget_autopos (g, found_num_label, WPOS_KEEP_BOTTOM, NULL);
+
+ status_label = label_new (y++, 4, _("Searching"));
+ group_add_widget_autopos (g, status_label, WPOS_KEEP_BOTTOM, NULL);
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < fbuts_num; i++)
+ {
+ if (i == 3)
+ fbuts[3].button = fbuts[2].button;
+ else
+ {
+ fbuts[i].button =
+ WIDGET (button_new
+ (y, fbuts[i].x, fbuts[i].ret_cmd, fbuts[i].flags, fbuts[i].text,
+ fbuts[i].callback));
+ group_add_widget_autopos (g, fbuts[i].button, WPOS_KEEP_BOTTOM, NULL);
+ }
+
+ if (i == quit_button)
+ y++;
+ }
+
+ widget_select (WIDGET (find_list));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+run_process (void)
+{
+ int ret;
+
+ search_content_handle = mc_search_new (content_pattern, NULL);
+ if (search_content_handle)
+ {
+ search_content_handle->search_type =
+ options.content_regexp ? MC_SEARCH_T_REGEX : MC_SEARCH_T_NORMAL;
+ search_content_handle->is_case_sensitive = options.content_case_sens;
+ search_content_handle->whole_words = options.content_whole_words;
+#ifdef HAVE_CHARSET
+ search_content_handle->is_all_charsets = options.content_all_charsets;
+#endif
+ }
+ search_file_handle = mc_search_new (find_pattern, NULL);
+ search_file_handle->search_type = options.file_pattern ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ search_file_handle->is_case_sensitive = options.file_case_sens;
+#ifdef HAVE_CHARSET
+ search_file_handle->is_all_charsets = options.file_all_charsets;
+#endif
+ search_file_handle->is_entire_line = options.file_pattern;
+
+ resuming = FALSE;
+
+ widget_idle (WIDGET (find_dlg), TRUE);
+ ret = dlg_run (find_dlg);
+
+ mc_search_free (search_file_handle);
+ search_file_handle = NULL;
+ mc_search_free (search_content_handle);
+ search_content_handle = NULL;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_gui (void)
+{
+ Widget *w = WIDGET (find_dlg);
+
+ widget_idle (w, FALSE);
+ widget_destroy (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+do_find (WPanel * panel, const char *start_dir, ssize_t start_dir_len, const char *ignore_dirs,
+ char **dirname, char **filename)
+{
+ int return_value = 0;
+ char *dir_tmp = NULL, *file_tmp = NULL;
+
+ setup_gui ();
+
+ init_find_vars ();
+ parse_ignore_dirs (ignore_dirs);
+ push_directory (vfs_path_from_str (start_dir));
+
+ return_value = run_process ();
+
+ /* Clear variables */
+ init_find_vars ();
+
+ get_list_info (&file_tmp, &dir_tmp, NULL, NULL);
+
+ if (dir_tmp)
+ *dirname = g_strdup (dir_tmp);
+ if (file_tmp)
+ *filename = g_strdup (file_tmp);
+
+ if (return_value == B_PANELIZE && *filename)
+ {
+ struct stat st;
+ GList *entry;
+ dir_list *list = &panel->dir;
+ char *name = NULL;
+ gboolean ok = TRUE;
+
+ panel_clean_dir (panel);
+ dir_list_init (list);
+
+ for (entry = listbox_get_first_link (find_list); entry != NULL && ok;
+ entry = g_list_next (entry))
+ {
+ const char *lc_filename = NULL;
+ WLEntry *le = LENTRY (entry->data);
+ find_match_location_t *location = le->data;
+ char *p;
+ gboolean link_to_dir, stale_link;
+
+ if ((le->text == NULL) || (location == NULL) || (location->dir == NULL))
+ continue;
+
+ if (!content_is_empty)
+ lc_filename = strchr (le->text + 4, ':') + 1;
+ else
+ lc_filename = le->text + 4;
+
+ name = mc_build_filename (location->dir, lc_filename, (char *) NULL);
+ /* skip initial start dir */
+ if (start_dir_len < 0)
+ p = name;
+ else
+ {
+ p = name + (size_t) start_dir_len;
+ if (IS_PATH_SEP (*p))
+ p++;
+ }
+
+ if (!handle_path (p, &st, &link_to_dir, &stale_link))
+ {
+ g_free (name);
+ continue;
+ }
+
+ /* don't add files more than once to the panel */
+ if (!content_is_empty && list->len != 0
+ && strcmp (list->list[list->len - 1].fname->str, p) == 0)
+ {
+ g_free (name);
+ continue;
+ }
+
+ ok = dir_list_append (list, p, &st, link_to_dir, stale_link);
+
+ g_free (name);
+
+ if ((list->len & 15) == 0)
+ rotate_dash (TRUE);
+ }
+
+ panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (panel);
+ panel_panelize_save (panel);
+ }
+
+ kill_gui ();
+ do_search (NULL); /* force do_search to release resources */
+ MC_PTR_FREE (old_dir);
+ rotate_dash (FALSE);
+
+ return return_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+find_cmd (WPanel * panel)
+{
+ char *start_dir = NULL, *ignore_dirs = NULL;
+ ssize_t start_dir_len;
+
+ find_pattern = NULL;
+ content_pattern = NULL;
+
+ while (find_parameters (panel, &start_dir, &start_dir_len,
+ &ignore_dirs, &find_pattern, &content_pattern))
+ {
+ char *filename = NULL, *dirname = NULL;
+ int v = B_CANCEL;
+
+ content_is_empty = content_pattern == NULL;
+
+ if (find_pattern[0] != '\0')
+ {
+ last_refresh = 0;
+
+ is_start = FALSE;
+
+ if (!content_is_empty && !str_is_valid_string (content_pattern))
+ MC_PTR_FREE (content_pattern);
+
+ v = do_find (panel, start_dir, start_dir_len, ignore_dirs, &dirname, &filename);
+ }
+
+ g_free (start_dir);
+ g_free (ignore_dirs);
+ MC_PTR_FREE (find_pattern);
+
+ if (v == B_ENTER)
+ {
+ if (dirname != NULL)
+ {
+ vfs_path_t *dirname_vpath;
+
+ dirname_vpath = vfs_path_from_str (dirname);
+ panel_cd (panel, dirname_vpath, cd_exact);
+ vfs_path_free (dirname_vpath, TRUE);
+ /* *INDENT-OFF* */
+ if (filename != NULL)
+ panel_set_current_by_name (panel,
+ filename + (content_pattern != NULL
+ ? strchr (filename + 4, ':') - filename + 1
+ : 4));
+ /* *INDENT-ON* */
+ }
+ else if (filename != NULL)
+ {
+ vfs_path_t *filename_vpath;
+
+ filename_vpath = vfs_path_from_str (filename);
+ panel_cd (panel, filename_vpath, cd_exact);
+ vfs_path_free (filename_vpath, TRUE);
+ }
+ }
+
+ MC_PTR_FREE (content_pattern);
+ g_free (dirname);
+ g_free (filename);
+
+ if (v == B_ENTER || v == B_CANCEL)
+ break;
+
+ if (v == B_PANELIZE)
+ {
+ panel_re_sort (panel);
+ panel_set_current_by_name (panel, NULL);
+ break;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/hotlist.c b/src/filemanager/hotlist.c
new file mode 100644
index 0000000..fa04a3b
--- /dev/null
+++ b/src/filemanager/hotlist.c
@@ -0,0 +1,1733 @@
+/*
+ Directory hotlist -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Radek Doulik, 1994
+ Janne Kukonlehto, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ Andrew Borodin <aborodin@vmail.ru>, 2012-2022
+
+ Janne did the original Hotlist code, Andrej made the groupable
+ hotlist; the move hotlist and revamped the file format and made
+ it stronger.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file hotlist.c
+ * \brief Source: directory hotlist
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* COLS */
+#include "lib/tty/key.h" /* KEY_M_CTRL */
+#include "lib/skin.h" /* colors */
+#include "lib/mcconfig.h" /* Load/save directories hotlist */
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/vfs/vfs.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* For profile_bname */
+#include "src/history.h"
+
+#include "command.h" /* cmdline */
+
+#include "hotlist.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UX 3
+#define UY 2
+
+#define B_ADD_CURRENT B_USER
+#define B_REMOVE (B_USER + 1)
+#define B_NEW_GROUP (B_USER + 2)
+#define B_NEW_ENTRY (B_USER + 3)
+#define B_ENTER_GROUP (B_USER + 4)
+#define B_UP_GROUP (B_USER + 5)
+#define B_INSERT (B_USER + 6)
+#define B_APPEND (B_USER + 7)
+#define B_MOVE (B_USER + 8)
+#ifdef ENABLE_VFS
+#define B_FREE_ALL_VFS (B_USER + 9)
+#define B_REFRESH_VFS (B_USER + 10)
+#endif
+
+#define TKN_GROUP 0
+#define TKN_ENTRY 1
+#define TKN_STRING 2
+#define TKN_URL 3
+#define TKN_ENDGROUP 4
+#define TKN_COMMENT 5
+#define TKN_EOL 125
+#define TKN_EOF 126
+#define TKN_UNKNOWN 127
+
+#define SKIP_TO_EOL \
+{ \
+ int _tkn; \
+ while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \
+}
+
+#define CHECK_TOKEN(_TKN_) \
+tkn = hot_next_token (); \
+if (tkn != _TKN_) \
+{ \
+ hotlist_state.readonly = TRUE; \
+ hotlist_state.file_error = TRUE; \
+ while (tkn != TKN_EOL && tkn != TKN_EOF) \
+ tkn = hot_next_token (); \
+ break; \
+}
+
+/*** file scope type declarations ****************************************************************/
+
+enum HotListType
+{
+ HL_TYPE_GROUP,
+ HL_TYPE_ENTRY,
+ HL_TYPE_COMMENT,
+ HL_TYPE_DOTDOT
+};
+
+static struct
+{
+ /*
+ * these reflect run time state
+ */
+
+ gboolean loaded; /* hotlist is loaded */
+ gboolean readonly; /* hotlist readonly */
+ gboolean file_error; /* parse error while reading file */
+ gboolean running; /* we are running dlg (and have to
+ update listbox */
+ gboolean moving; /* we are in moving hotlist currently */
+ gboolean modified; /* hotlist was modified */
+ hotlist_t type; /* LIST_HOTLIST || LIST_VFSLIST */
+} hotlist_state;
+
+/* Directory hotlist */
+struct hotlist
+{
+ enum HotListType type;
+ char *directory;
+ char *label;
+ struct hotlist *head;
+ struct hotlist *up;
+ struct hotlist *next;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WPanel *our_panel;
+
+static gboolean hotlist_has_dot_dot = TRUE;
+
+static WDialog *hotlist_dlg, *movelist_dlg;
+static WGroupbox *hotlist_group, *movelist_group;
+static WListbox *l_hotlist, *l_movelist;
+static WLabel *pname;
+
+static struct
+{
+ int ret_cmd, flags, y, x, len;
+ const char *text;
+ int type;
+ widget_pos_flags_t pos_flags;
+} hotlist_but[] =
+{
+ /* *INDENT-OFF* */
+ { B_ENTER, DEFPUSH_BUTTON, 0, 0, 0, N_("Change &to"),
+ LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+#ifdef ENABLE_VFS
+ { B_FREE_ALL_VFS, NORMAL_BUTTON, 0, 20, 0, N_("&Free VFSs now"),
+ LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_REFRESH_VFS, NORMAL_BUTTON, 0, 43, 0, N_("&Refresh"),
+ LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+#endif
+ { B_ADD_CURRENT, NORMAL_BUTTON, 0, 20, 0, N_("&Add current"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_UP_GROUP, NORMAL_BUTTON, 0, 42, 0, N_("&Up"),
+ LIST_HOTLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_CANCEL, NORMAL_BUTTON, 0, 53, 0, N_("&Cancel"),
+ LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_RIGHT | WPOS_KEEP_BOTTOM },
+ { B_NEW_GROUP, NORMAL_BUTTON, 1, 0, 0, N_("New &group"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_NEW_ENTRY, NORMAL_BUTTON, 1, 15, 0, N_("New &entry"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_INSERT, NORMAL_BUTTON, 1, 0, 0, N_("&Insert"),
+ LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_APPEND, NORMAL_BUTTON, 1, 15, 0, N_("A&ppend"),
+ LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_REMOVE, NORMAL_BUTTON, 1, 30, 0, N_("&Remove"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
+ { B_MOVE, NORMAL_BUTTON, 1, 42, 0, N_("&Move"),
+ LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }
+ /* *INDENT-ON* */
+};
+
+static const size_t hotlist_but_num = G_N_ELEMENTS (hotlist_but);
+
+static struct hotlist *hotlist = NULL;
+
+static struct hotlist *current_group;
+
+static GString *tkn_buf = NULL;
+
+static char *hotlist_file_name;
+static FILE *hotlist_file;
+static time_t hotlist_file_mtime;
+
+static int list_level = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void init_movelist (struct hotlist *item);
+static void add_new_group_cmd (void);
+static void add_new_entry_cmd (WPanel * panel);
+static void remove_from_hotlist (struct hotlist *entry);
+static void load_hotlist (void);
+static void add_dotdot_to_list (void);
+
+/* --------------------------------------------------------------------------------------------- */
+/** If current->data is 0, then we are dealing with a VFS pathname */
+
+static void
+update_path_name (void)
+{
+ const char *text = "";
+ char *p;
+ WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
+ Widget *w = WIDGET (list);
+
+ if (!listbox_is_empty (list))
+ {
+ char *ctext = NULL;
+ void *cdata = NULL;
+
+ listbox_get_current (list, &ctext, &cdata);
+ if (cdata == NULL)
+ text = ctext;
+ else
+ {
+ struct hotlist *hlp = (struct hotlist *) cdata;
+
+ if (hlp->type == HL_TYPE_ENTRY || hlp->type == HL_TYPE_DOTDOT)
+ text = hlp->directory;
+ else if (hlp->type == HL_TYPE_GROUP)
+ text = _("Subgroup - press ENTER to see list");
+ }
+ }
+
+ p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
+ if (hotlist_state.moving)
+ groupbox_set_title (movelist_group, str_trunc (p, w->rect.cols - 2));
+ else
+ {
+ groupbox_set_title (hotlist_group, str_trunc (p, w->rect.cols - 2));
+ label_set_text (pname, str_trunc (text, w->rect.cols));
+ }
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fill_listbox (WListbox * list)
+{
+ struct hotlist *current;
+ GString *buff;
+
+ buff = g_string_new ("");
+
+ for (current = current_group->head; current != NULL; current = current->next)
+ switch (current->type)
+ {
+ case HL_TYPE_GROUP:
+ {
+ /* buff clean up */
+ g_string_truncate (buff, 0);
+ g_string_append (buff, "->");
+ g_string_append (buff, current->label);
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, buff->str, current, FALSE);
+ }
+ break;
+ case HL_TYPE_DOTDOT:
+ case HL_TYPE_ENTRY:
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ g_string_free (buff, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+unlink_entry (struct hotlist *entry)
+{
+ struct hotlist *current = current_group->head;
+
+ if (current == entry)
+ current_group->head = entry->next;
+ else
+ {
+ while (current != NULL && current->next != entry)
+ current = current->next;
+ if (current != NULL)
+ current->next = entry->next;
+ }
+ entry->next = entry->up = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_VFS
+static void
+add_name_to_list (const char *path)
+{
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, path, NULL, FALSE);
+}
+#endif /* !ENABLE_VFS */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hotlist_run_cmd (int action)
+{
+ switch (action)
+ {
+ case B_MOVE:
+ {
+ struct hotlist *saved = current_group;
+ struct hotlist *item = NULL;
+ struct hotlist *moveto_item = NULL;
+ struct hotlist *moveto_group = NULL;
+ int ret;
+
+ if (listbox_is_empty (l_hotlist))
+ return 0; /* empty group - nothing to do */
+
+ listbox_get_current (l_hotlist, NULL, (void **) &item);
+ init_movelist (item);
+ hotlist_state.moving = TRUE;
+ ret = dlg_run (movelist_dlg);
+ hotlist_state.moving = FALSE;
+ listbox_get_current (l_movelist, NULL, (void **) &moveto_item);
+ moveto_group = current_group;
+ widget_destroy (WIDGET (movelist_dlg));
+ current_group = saved;
+ if (ret == B_CANCEL)
+ return 0;
+ if (moveto_item == item)
+ return 0; /* If we insert/append a before/after a
+ it hardly changes anything ;) */
+ unlink_entry (item);
+ listbox_remove_current (l_hotlist);
+ item->up = moveto_group;
+ if (moveto_group->head == NULL)
+ moveto_group->head = item;
+ else if (moveto_item == NULL)
+ { /* we have group with just comments */
+ struct hotlist *p = moveto_group->head;
+
+ /* skip comments */
+ while (p->next != NULL)
+ p = p->next;
+ p->next = item;
+ }
+ else if (ret == B_ENTER || ret == B_APPEND)
+ {
+ if (moveto_item->next == NULL)
+ moveto_item->next = item;
+ else
+ {
+ item->next = moveto_item->next;
+ moveto_item->next = item;
+ }
+ }
+ else if (moveto_group->head == moveto_item)
+ {
+ moveto_group->head = item;
+ item->next = moveto_item;
+ }
+ else
+ {
+ struct hotlist *p = moveto_group->head;
+
+ while (p->next != moveto_item)
+ p = p->next;
+ item->next = p->next;
+ p->next = item;
+ }
+ listbox_remove_list (l_hotlist);
+ fill_listbox (l_hotlist);
+ repaint_screen ();
+ hotlist_state.modified = TRUE;
+ return 0;
+ }
+ case B_REMOVE:
+ {
+ struct hotlist *entry = NULL;
+
+ listbox_get_current (l_hotlist, NULL, (void **) &entry);
+ remove_from_hotlist (entry);
+ }
+ return 0;
+
+ case B_NEW_GROUP:
+ add_new_group_cmd ();
+ return 0;
+
+ case B_ADD_CURRENT:
+ add2hotlist_cmd (our_panel);
+ return 0;
+
+ case B_NEW_ENTRY:
+ add_new_entry_cmd (our_panel);
+ return 0;
+
+ case B_ENTER:
+ case B_ENTER_GROUP:
+ {
+ WListbox *list;
+ void *data;
+ struct hotlist *hlp;
+
+ list = hotlist_state.moving ? l_movelist : l_hotlist;
+ listbox_get_current (list, NULL, &data);
+
+ if (data == NULL)
+ return 1;
+
+ hlp = (struct hotlist *) data;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ return (action == B_ENTER ? 1 : 0);
+ if (hlp->type != HL_TYPE_DOTDOT)
+ {
+ listbox_remove_list (list);
+ current_group = hlp;
+ fill_listbox (list);
+ return 0;
+ }
+ }
+ MC_FALLTHROUGH; /* if list empty - just go up */
+
+ case B_UP_GROUP:
+ {
+ WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
+
+ listbox_remove_list (list);
+ current_group = current_group->up;
+ fill_listbox (list);
+ return 0;
+ }
+
+#ifdef ENABLE_VFS
+ case B_FREE_ALL_VFS:
+ vfs_expire (TRUE);
+ MC_FALLTHROUGH;
+
+ case B_REFRESH_VFS:
+ listbox_remove_list (l_hotlist);
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
+ FALSE);
+ vfs_fill_names (add_name_to_list);
+ return 0;
+#endif /* ENABLE_VFS */
+
+ default:
+ return 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hotlist_button_callback (WButton * button, int action)
+{
+ int ret;
+
+ (void) button;
+ ret = hotlist_run_cmd (action);
+ update_path_name ();
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline cb_ret_t
+hotlist_handle_key (WDialog * h, int key)
+{
+ switch (key)
+ {
+ case KEY_M_CTRL | '\n':
+ goto l1;
+
+ case '\n':
+ case KEY_ENTER:
+ if (hotlist_button_callback (NULL, B_ENTER) != 0)
+ {
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+ return MSG_HANDLED;
+
+ case KEY_RIGHT:
+ /* enter to the group */
+ if (hotlist_state.type == LIST_VFSLIST)
+ return MSG_NOT_HANDLED;
+ return hotlist_button_callback (NULL, B_ENTER_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case KEY_LEFT:
+ /* leave the group */
+ if (hotlist_state.type == LIST_VFSLIST)
+ return MSG_NOT_HANDLED;
+ return hotlist_button_callback (NULL, B_UP_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
+
+ case KEY_DC:
+ if (hotlist_state.moving)
+ return MSG_NOT_HANDLED;
+ hotlist_button_callback (NULL, B_REMOVE);
+ return MSG_HANDLED;
+
+ l1:
+ case ALT ('\n'):
+ case ALT ('\r'):
+ if (!hotlist_state.moving)
+ {
+ void *ldata = NULL;
+
+ listbox_get_current (l_hotlist, NULL, &ldata);
+
+ if (ldata != NULL)
+ {
+ struct hotlist *hlp = (struct hotlist *) ldata;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ {
+ char *tmp;
+
+ tmp = g_strconcat ("cd ", hlp->directory, (char *) NULL);
+ input_insert (cmdline, tmp, FALSE);
+ g_free (tmp);
+ h->ret_value = B_CANCEL;
+ dlg_close (h);
+ }
+ }
+ }
+ return MSG_HANDLED; /* ignore key */
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+hotlist_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
+ update_path_name ();
+ return MSG_HANDLED;
+
+ case MSG_UNHANDLED_KEY:
+ return hotlist_handle_key (h, parm);
+
+ case MSG_POST_KEY:
+ /*
+ * The code here has two purposes:
+ *
+ * (1) Always stay on the hotlist.
+ *
+ * Activating a button using its hotkey (and even pressing ENTER, as
+ * there's a "default button") moves the focus to the button. But we
+ * want to stay on the hotlist, to be able to use the usual keys (up,
+ * down, etc.). So we do `widget_select (lst)`.
+ *
+ * (2) Refresh the hotlist.
+ *
+ * We may have run a command that changed the contents of the list.
+ * We therefore need to refresh it. So we do `widget_draw (lst)`.
+ */
+ {
+ Widget *lst;
+
+ lst = WIDGET (h == hotlist_dlg ? l_hotlist : l_movelist);
+
+ /* widget_select() already redraws the widget, but since it's a
+ * no-op if the widget is already selected ("focused"), we have
+ * to call widget_draw() separately. */
+ if (!widget_get_state (lst, WST_FOCUSED))
+ widget_select (lst);
+ else
+ widget_draw (lst);
+ }
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ {
+ WRect r = w->rect;
+
+ r.lines = LINES - (h == hotlist_dlg ? 2 : 6);
+ r.cols = COLS - 6;
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static lcback_ret_t
+hotlist_listbox_callback (WListbox * list)
+{
+ WDialog *dlg = DIALOG (WIDGET (list)->owner);
+
+ if (!listbox_is_empty (list))
+ {
+ void *data = NULL;
+
+ listbox_get_current (list, NULL, &data);
+
+ if (data != NULL)
+ {
+ struct hotlist *hlp = (struct hotlist *) data;
+
+ if (hlp->type == HL_TYPE_ENTRY)
+ {
+ dlg->ret_value = B_ENTER;
+ dlg_close (dlg);
+ return LISTBOX_DONE;
+ }
+ else
+ {
+ hotlist_button_callback (NULL, B_ENTER);
+ send_message (dlg, NULL, MSG_POST_KEY, '\n', NULL);
+ return LISTBOX_CONT;
+ }
+ }
+ else
+ {
+ dlg->ret_value = B_ENTER;
+ dlg_close (dlg);
+ return LISTBOX_DONE;
+ }
+ }
+
+ hotlist_button_callback (NULL, B_UP_GROUP);
+ send_message (dlg, NULL, MSG_POST_KEY, 'u', NULL);
+ return LISTBOX_CONT;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Expands all button names (once) and recalculates button positions.
+ * returns number of columns in the dialog box, which is 10 chars longer
+ * then buttonbar.
+ *
+ * If common width of the window (i.e. in xterm) is less than returned
+ * width - sorry :) (anyway this did not handled in previous version too)
+ */
+
+static int
+init_i18n_stuff (int list_type, int cols)
+{
+ size_t i;
+
+ static gboolean i18n_flag = FALSE;
+
+ if (!i18n_flag)
+ {
+ for (i = 0; i < hotlist_but_num; i++)
+ {
+#ifdef ENABLE_NLS
+ hotlist_but[i].text = _(hotlist_but[i].text);
+#endif /* ENABLE_NLS */
+ hotlist_but[i].len = str_term_width1 (hotlist_but[i].text) + 3;
+ if (hotlist_but[i].flags == DEFPUSH_BUTTON)
+ hotlist_but[i].len += 2;
+ }
+
+ i18n_flag = TRUE;
+ }
+
+ /* Dynamic resizing of buttonbars */
+ {
+ int len[2], count[2]; /* at most two lines of buttons */
+ int cur_x[2];
+
+ len[0] = len[1] = 0;
+ count[0] = count[1] = 0;
+ cur_x[0] = cur_x[1] = 0;
+
+ /* Count len of buttonbars, assuming 1 extra space between buttons */
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ {
+ int row;
+
+ row = hotlist_but[i].y;
+ ++count[row];
+ len[row] += hotlist_but[i].len + 1;
+ }
+
+ (len[0])--;
+ (len[1])--;
+
+ cols = MAX (cols, MAX (len[0], len[1]));
+
+ /* arrange buttons */
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ {
+ int row;
+
+ row = hotlist_but[i].y;
+
+ if (hotlist_but[i].x != 0)
+ {
+ /* not first int the row */
+ if (hotlist_but[i].ret_cmd == B_CANCEL)
+ hotlist_but[i].x = cols - hotlist_but[i].len - 6;
+ else
+ hotlist_but[i].x = cur_x[row];
+ }
+
+ cur_x[row] += hotlist_but[i].len + 1;
+ }
+ }
+
+ return cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_hotlist (hotlist_t list_type)
+{
+ size_t i;
+ const char *title, *help_node;
+ int lines, cols;
+ int y;
+ int dh = 0;
+ WGroup *g;
+ WGroupbox *path_box;
+ Widget *hotlist_widget;
+
+ do_refresh ();
+
+ lines = LINES - 2;
+ cols = init_i18n_stuff (list_type, COLS - 6);
+
+#ifdef ENABLE_VFS
+ if (list_type == LIST_VFSLIST)
+ {
+ title = _("Active VFS directories");
+ help_node = "[vfshot]"; /* FIXME - no such node */
+ dh = 1;
+ }
+ else
+#endif /* !ENABLE_VFS */
+ {
+ title = _("Directory hotlist");
+ help_node = "[Hotlist]";
+ }
+
+ hotlist_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
+ NULL, help_node, title);
+ g = GROUP (hotlist_dlg);
+
+ y = UY;
+ hotlist_group = groupbox_new (y, UX, lines - 10 + dh, cols - 2 * UX, _("Top level group"));
+ hotlist_widget = WIDGET (hotlist_group);
+ group_add_widget_autopos (g, hotlist_widget, WPOS_KEEP_ALL, NULL);
+
+ l_hotlist =
+ listbox_new (y + 1, UX + 1, hotlist_widget->rect.lines - 2, hotlist_widget->rect.cols - 2,
+ FALSE, hotlist_listbox_callback);
+
+ /* Fill the hotlist with the active VFS or the hotlist */
+#ifdef ENABLE_VFS
+ if (list_type == LIST_VFSLIST)
+ {
+ listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
+ FALSE);
+ vfs_fill_names (add_name_to_list);
+ }
+ else
+#endif /* !ENABLE_VFS */
+ fill_listbox (l_hotlist);
+
+ /* insert before groupbox to view scrollbar */
+ group_add_widget_autopos (g, l_hotlist, WPOS_KEEP_ALL, NULL);
+
+ y += hotlist_widget->rect.lines;
+
+ path_box = groupbox_new (y, UX, 3, hotlist_widget->rect.cols, _("Directory path"));
+ group_add_widget_autopos (g, path_box, WPOS_KEEP_BOTTOM | WPOS_KEEP_HORZ, NULL);
+
+ pname = label_new (y + 1, UX + 2, NULL);
+ group_add_widget_autopos (g, pname, WPOS_KEEP_BOTTOM | WPOS_KEEP_LEFT, NULL);
+ y += WIDGET (path_box)->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & list_type) != 0)
+ group_add_widget_autopos (g,
+ button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
+ hotlist_but[i].ret_cmd, hotlist_but[i].flags,
+ hotlist_but[i].text, hotlist_button_callback),
+ hotlist_but[i].pos_flags, NULL);
+
+ widget_select (WIDGET (l_hotlist));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+init_movelist (struct hotlist *item)
+{
+ size_t i;
+ char *hdr;
+ int lines, cols;
+ int y;
+ WGroup *g;
+ Widget *movelist_widget;
+
+ do_refresh ();
+
+ lines = LINES - 6;
+ cols = init_i18n_stuff (LIST_MOVELIST, COLS - 6);
+
+ hdr = g_strdup_printf (_("Moving %s"), item->label);
+
+ movelist_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
+ NULL, "[Hotlist]", hdr);
+ g = GROUP (movelist_dlg);
+
+ g_free (hdr);
+
+ y = UY;
+ movelist_group = groupbox_new (y, UX, lines - 7, cols - 2 * UX, _("Directory label"));
+ movelist_widget = WIDGET (movelist_group);
+ group_add_widget_autopos (g, movelist_widget, WPOS_KEEP_ALL, NULL);
+
+ l_movelist =
+ listbox_new (y + 1, UX + 1, movelist_widget->rect.lines - 2, movelist_widget->rect.cols - 2,
+ FALSE, hotlist_listbox_callback);
+ fill_listbox (l_movelist);
+ /* insert before groupbox to view scrollbar */
+ group_add_widget_autopos (g, l_movelist, WPOS_KEEP_ALL, NULL);
+
+ y += movelist_widget->rect.lines;
+
+ group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
+
+ for (i = 0; i < hotlist_but_num; i++)
+ if ((hotlist_but[i].type & LIST_MOVELIST) != 0)
+ group_add_widget_autopos (g,
+ button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
+ hotlist_but[i].ret_cmd, hotlist_but[i].flags,
+ hotlist_but[i].text, hotlist_button_callback),
+ hotlist_but[i].pos_flags, NULL);
+
+ widget_select (WIDGET (l_movelist));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy the list dialog.
+ * Don't confuse with done_hotlist() for the list in memory.
+ */
+
+static void
+hotlist_done (void)
+{
+ widget_destroy (WIDGET (hotlist_dlg));
+ l_hotlist = NULL;
+#if 0
+ update_panels (UP_OPTIMIZE, UP_KEEPSEL);
+#endif
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+find_group_section (struct hotlist *grp)
+{
+ return g_strconcat (grp->directory, ".Group", (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct hotlist *
+add2hotlist (char *label, char *directory, enum HotListType type, listbox_append_t pos)
+{
+ struct hotlist *new;
+ struct hotlist *current = NULL;
+
+ /*
+ * Hotlist is neither loaded nor loading.
+ * Must be called by "Ctrl-x a" before using hotlist.
+ */
+ if (current_group == NULL)
+ load_hotlist ();
+
+ listbox_get_current (l_hotlist, NULL, (void **) &current);
+
+ /* Make sure '..' stays at the top of the list. */
+ if ((current != NULL) && (current->type == HL_TYPE_DOTDOT))
+ pos = LISTBOX_APPEND_AFTER;
+
+ new = g_new0 (struct hotlist, 1);
+
+ new->type = type;
+ new->label = label;
+ new->directory = directory;
+ new->up = current_group;
+
+ if (type == HL_TYPE_GROUP)
+ {
+ current_group = new;
+ add_dotdot_to_list ();
+ current_group = new->up;
+ }
+
+ if (current_group->head == NULL)
+ {
+ /* first element in group */
+ current_group->head = new;
+ }
+ else if (pos == LISTBOX_APPEND_AFTER)
+ {
+ new->next = current->next;
+ current->next = new;
+ }
+ else if (pos == LISTBOX_APPEND_BEFORE && current == current_group->head)
+ {
+ /* should be inserted before first item */
+ new->next = current;
+ current_group->head = new;
+ }
+ else if (pos == LISTBOX_APPEND_BEFORE)
+ {
+ struct hotlist *p = current_group->head;
+
+ while (p->next != current)
+ p = p->next;
+
+ new->next = current;
+ p->next = new;
+ }
+ else
+ { /* append at the end */
+ struct hotlist *p = current_group->head;
+
+ while (p->next != NULL)
+ p = p->next;
+
+ p->next = new;
+ }
+
+ if (hotlist_state.running && type != HL_TYPE_COMMENT && type != HL_TYPE_DOTDOT)
+ {
+ if (type == HL_TYPE_GROUP)
+ {
+ char *lbl;
+
+ lbl = g_strconcat ("->", new->label, (char *) NULL);
+ listbox_add_item (l_hotlist, pos, 0, lbl, new, FALSE);
+ g_free (lbl);
+ }
+ else
+ listbox_add_item (l_hotlist, pos, 0, new->label, new, FALSE);
+ listbox_set_current (l_hotlist, l_hotlist->current);
+ }
+
+ return new;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+add_new_entry_input (const char *header, const char *text1, const char *text2,
+ const char *help, char **r1, char **r2)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (text2, input_label_above, *r2, "input-lbl", r2, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
+ QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, help,
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ ret = quick_dialog (&qdlg);
+
+ return (ret != B_CANCEL) ? ret : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_new_entry_cmd (WPanel * panel)
+{
+ char *title, *url, *to_free;
+ int ret;
+
+ /* Take current directory as default value for input fields */
+ to_free = title = url = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+
+ ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"),
+ _("Directory path:"), "[Hotlist]", &title, &url);
+ g_free (to_free);
+
+ if (ret == 0)
+ return;
+ if (title == NULL || *title == '\0' || url == NULL || *url == '\0')
+ {
+ g_free (title);
+ g_free (url);
+ return;
+ }
+
+ if (ret == B_ENTER || ret == B_APPEND)
+ add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER);
+ else
+ add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE);
+
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+add_new_group_input (const char *header, const char *label, char **result)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_START_BUTTONS (TRUE, TRUE),
+ QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
+ QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, "[Hotlist]",
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ ret = quick_dialog (&qdlg);
+
+ return (ret != B_CANCEL) ? ret : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_new_group_cmd (void)
+{
+ char *label;
+ int ret;
+
+ ret = add_new_group_input (_("New hotlist group"), _("Name of new group:"), &label);
+ if (ret == 0 || label == NULL || *label == '\0')
+ return;
+
+ if (ret == B_ENTER || ret == B_APPEND)
+ add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER);
+ else
+ add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE);
+
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_group (struct hotlist *grp)
+{
+ struct hotlist *current = grp->head;
+
+ while (current != NULL)
+ {
+ struct hotlist *next = current->next;
+
+ if (current->type == HL_TYPE_GROUP)
+ remove_group (current);
+
+ g_free (current->label);
+ g_free (current->directory);
+ g_free (current);
+
+ current = next;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_from_hotlist (struct hotlist *entry)
+{
+ if (entry == NULL)
+ return;
+
+ if (entry->type == HL_TYPE_DOTDOT)
+ return;
+
+ if (confirm_directory_hotlist_delete)
+ {
+ char text[BUF_MEDIUM];
+ int result;
+
+ if (safe_delete)
+ query_set_sel (1);
+
+ g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"),
+ str_trunc (entry->label, 30));
+ result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
+ _("&Yes"), _("&No"));
+ if (result != 0)
+ return;
+ }
+
+ if (entry->type == HL_TYPE_GROUP)
+ {
+ struct hotlist *head = entry->head;
+
+ if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL))
+ {
+ char text[BUF_MEDIUM];
+ int result;
+
+ g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"),
+ str_trunc (entry->label, 30));
+ result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
+ _("&Yes"), _("&No"));
+ if (result != 0)
+ return;
+ }
+
+ remove_group (entry);
+ }
+
+ unlink_entry (entry);
+
+ g_free (entry->label);
+ g_free (entry->directory);
+ g_free (entry);
+ /* now remove list entry from screen */
+ listbox_remove_current (l_hotlist);
+ hotlist_state.modified = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_group (struct hotlist *grp)
+{
+ gchar **profile_keys, **keys;
+ char *group_section;
+ struct hotlist *current = 0;
+
+ group_section = find_group_section (grp);
+
+ keys = mc_config_get_keys (mc_global.main_config, group_section, NULL);
+
+ current_group = grp;
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
+ g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END);
+
+ g_strfreev (keys);
+
+ keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
+ g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+
+ g_free (group_section);
+ g_strfreev (keys);
+
+ for (current = grp->head; current; current = current->next)
+ load_group (current);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hot_skip_blanks (void)
+{
+ int c;
+
+ while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c))
+ ;
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+hot_next_token (void)
+{
+ int c, ret = 0;
+ size_t l;
+
+ if (tkn_buf == NULL)
+ tkn_buf = g_string_new ("");
+ g_string_set_size (tkn_buf, 0);
+
+ again:
+ c = hot_skip_blanks ();
+ switch (c)
+ {
+ case EOF:
+ ret = TKN_EOF;
+ break;
+ case '\n':
+ ret = TKN_EOL;
+ break;
+ case '#':
+ while ((c = getc (hotlist_file)) != EOF && c != '\n')
+ g_string_append_c (tkn_buf, c);
+ ret = TKN_COMMENT;
+ break;
+ case '"':
+ while ((c = getc (hotlist_file)) != EOF && c != '"')
+ {
+ if (c == '\\')
+ {
+ c = getc (hotlist_file);
+ if (c == EOF)
+ {
+ g_string_free (tkn_buf, TRUE);
+ return TKN_EOF;
+ }
+ }
+ g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
+ }
+ ret = (c == EOF) ? TKN_EOF : TKN_STRING;
+ break;
+ case '\\':
+ c = getc (hotlist_file);
+ if (c == EOF)
+ {
+ g_string_free (tkn_buf, TRUE);
+ return TKN_EOF;
+ }
+ if (c == '\n')
+ goto again;
+
+ MC_FALLTHROUGH; /* it is taken as normal character */
+
+ default:
+ do
+ {
+ g_string_append_c (tkn_buf, g_ascii_toupper (c));
+ }
+ while ((c = fgetc (hotlist_file)) != EOF && (g_ascii_isalnum (c) || !isascii (c)));
+ if (c != EOF)
+ ungetc (c, hotlist_file);
+ l = tkn_buf->len;
+ if (strncmp (tkn_buf->str, "GROUP", l) == 0)
+ ret = TKN_GROUP;
+ else if (strncmp (tkn_buf->str, "ENTRY", l) == 0)
+ ret = TKN_ENTRY;
+ else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0)
+ ret = TKN_ENDGROUP;
+ else if (strncmp (tkn_buf->str, "URL", l) == 0)
+ ret = TKN_URL;
+ else
+ ret = TKN_UNKNOWN;
+ break;
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_load_group (struct hotlist *grp)
+{
+ int tkn;
+ struct hotlist *new_grp;
+ char *label, *url;
+
+ current_group = grp;
+
+ while ((tkn = hot_next_token ()) != TKN_ENDGROUP)
+ switch (tkn)
+ {
+ case TKN_GROUP:
+ CHECK_TOKEN (TKN_STRING);
+ new_grp =
+ add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
+ LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ hot_load_group (new_grp);
+ current_group = grp;
+ break;
+ case TKN_ENTRY:
+ {
+ CHECK_TOKEN (TKN_STRING);
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ CHECK_TOKEN (TKN_URL);
+ CHECK_TOKEN (TKN_STRING);
+ url = tilde_expand (tkn_buf->str);
+ add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ }
+ break;
+ case TKN_COMMENT:
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
+ break;
+ case TKN_EOF:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ return;
+ case TKN_EOL:
+ /* skip empty lines */
+ break;
+ default:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ SKIP_TO_EOL;
+ break;
+ }
+ SKIP_TO_EOL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_load_file (struct hotlist *grp)
+{
+ int tkn;
+ struct hotlist *new_grp;
+ char *label, *url;
+
+ current_group = grp;
+
+ while ((tkn = hot_next_token ()) != TKN_EOF)
+ switch (tkn)
+ {
+ case TKN_GROUP:
+ CHECK_TOKEN (TKN_STRING);
+ new_grp =
+ add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
+ LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ hot_load_group (new_grp);
+ current_group = grp;
+ break;
+ case TKN_ENTRY:
+ {
+ CHECK_TOKEN (TKN_STRING);
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ CHECK_TOKEN (TKN_URL);
+ CHECK_TOKEN (TKN_STRING);
+ url = tilde_expand (tkn_buf->str);
+ add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ SKIP_TO_EOL;
+ }
+ break;
+ case TKN_COMMENT:
+ label = g_strndup (tkn_buf->str, tkn_buf->len);
+ add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
+ break;
+ case TKN_EOL:
+ /* skip empty lines */
+ break;
+ default:
+ hotlist_state.readonly = TRUE;
+ hotlist_state.file_error = TRUE;
+ SKIP_TO_EOL;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clean_up_hotlist_groups (const char *section)
+{
+ char *grp_section;
+
+ grp_section = g_strconcat (section, ".Group", (char *) NULL);
+ if (mc_config_has_group (mc_global.main_config, section))
+ mc_config_del_group (mc_global.main_config, section);
+
+ if (mc_config_has_group (mc_global.main_config, grp_section))
+ {
+ char **profile_keys, **keys;
+
+ keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL);
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ clean_up_hotlist_groups (*profile_keys);
+
+ g_strfreev (keys);
+ mc_config_del_group (mc_global.main_config, grp_section);
+ }
+ g_free (grp_section);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+load_hotlist (void)
+{
+ gboolean remove_old_list = FALSE;
+ struct stat stat_buf;
+
+ if (hotlist_state.loaded)
+ {
+ stat (hotlist_file_name, &stat_buf);
+ if (hotlist_file_mtime < stat_buf.st_mtime)
+ done_hotlist ();
+ else
+ return;
+ }
+
+ if (hotlist_file_name == NULL)
+ hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE);
+
+ hotlist = g_new0 (struct hotlist, 1);
+ hotlist->type = HL_TYPE_GROUP;
+ hotlist->label = g_strdup (_("Top level group"));
+ hotlist->up = hotlist;
+ /*
+ * compatibility :-(
+ */
+ hotlist->directory = g_strdup ("Hotlist");
+
+ hotlist_file = fopen (hotlist_file_name, "r");
+ if (hotlist_file == NULL)
+ {
+ int result;
+
+ load_group (hotlist);
+ hotlist_state.loaded = TRUE;
+ /*
+ * just to be sure we got copy
+ */
+ hotlist_state.modified = TRUE;
+ result = save_hotlist ();
+ hotlist_state.modified = FALSE;
+ if (result != 0)
+ remove_old_list = TRUE;
+ else
+ message (D_ERROR, _("Hotlist Load"),
+ _
+ ("MC was unable to write %s file,\nyour old hotlist entries were not deleted"),
+ MC_USERCONF_DIR PATH_SEP_STR MC_HOTLIST_FILE);
+ }
+ else
+ {
+ hot_load_file (hotlist);
+ fclose (hotlist_file);
+ hotlist_state.loaded = TRUE;
+ }
+
+ if (remove_old_list)
+ {
+ GError *mcerror = NULL;
+
+ clean_up_hotlist_groups ("Hotlist");
+ if (!mc_config_save_file (mc_global.main_config, &mcerror))
+ setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror);
+
+ mc_error_message (&mcerror, NULL);
+ }
+
+ stat (hotlist_file_name, &stat_buf);
+ hotlist_file_mtime = stat_buf.st_mtime;
+ current_group = hotlist;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hot_save_group (struct hotlist *grp)
+{
+ struct hotlist *current;
+ int i;
+ char *s;
+
+#define INDENT(n) \
+do { \
+ for (i = 0; i < n; i++) \
+ putc (' ', hotlist_file); \
+} while (0)
+
+ for (current = grp->head; current != NULL; current = current->next)
+ switch (current->type)
+ {
+ case HL_TYPE_GROUP:
+ INDENT (list_level);
+ fputs ("GROUP \"", hotlist_file);
+ for (s = current->label; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\"\n", hotlist_file);
+ list_level += 2;
+ hot_save_group (current);
+ list_level -= 2;
+ INDENT (list_level);
+ fputs ("ENDGROUP\n", hotlist_file);
+ break;
+ case HL_TYPE_ENTRY:
+ INDENT (list_level);
+ fputs ("ENTRY \"", hotlist_file);
+ for (s = current->label; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\" URL \"", hotlist_file);
+ for (s = current->directory; *s != '\0'; s++)
+ {
+ if (*s == '"' || *s == '\\')
+ putc ('\\', hotlist_file);
+ putc (*s, hotlist_file);
+ }
+ fputs ("\"\n", hotlist_file);
+ break;
+ case HL_TYPE_COMMENT:
+ fprintf (hotlist_file, "#%s\n", current->label);
+ break;
+ case HL_TYPE_DOTDOT:
+ /* do nothing */
+ break;
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_dotdot_to_list (void)
+{
+ if (current_group != hotlist && hotlist_has_dot_dot)
+ add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, LISTBOX_APPEND_AT_END);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+add2hotlist_cmd (WPanel * panel)
+{
+ char *lc_prompt;
+ const char *cp = N_("Label for \"%s\":");
+ int l;
+ char *label_string, *label;
+
+#ifdef ENABLE_NLS
+ cp = _(cp);
+#endif
+
+ /* extra variable to use it in the button callback */
+ our_panel = panel;
+
+ l = str_term_width1 (cp);
+ label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+ lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8)));
+ label =
+ input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string,
+ INPUT_COMPLETE_NONE);
+ g_free (lc_prompt);
+
+ if (label == NULL || *label == '\0')
+ {
+ g_free (label_string);
+ g_free (label);
+ }
+ else
+ {
+ add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
+ hotlist_state.modified = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+hotlist_show (hotlist_t list_type, WPanel * panel)
+{
+ char *target = NULL;
+ int res;
+
+ /* extra variable to use it in the button callback */
+ our_panel = panel;
+
+ hotlist_state.type = list_type;
+ load_hotlist ();
+
+ init_hotlist (list_type);
+
+ /* display file info */
+ tty_setcolor (SELECTED_COLOR);
+
+ hotlist_state.running = TRUE;
+ res = dlg_run (hotlist_dlg);
+ hotlist_state.running = FALSE;
+ save_hotlist ();
+
+ if (res == B_ENTER)
+ {
+ char *text = NULL;
+ struct hotlist *hlp = NULL;
+
+ listbox_get_current (l_hotlist, &text, (void **) &hlp);
+ target = g_strdup (hlp != NULL ? hlp->directory : text);
+ }
+
+ hotlist_done ();
+ return target;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+save_hotlist (void)
+{
+ gboolean saved = FALSE;
+ struct stat stat_buf;
+
+ if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL)
+ {
+ mc_util_make_backup_if_possible (hotlist_file_name, ".bak");
+
+ hotlist_file = fopen (hotlist_file_name, "w");
+ if (hotlist_file == NULL)
+ mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak");
+ else
+ {
+ hot_save_group (hotlist);
+ fclose (hotlist_file);
+ stat (hotlist_file_name, &stat_buf);
+ hotlist_file_mtime = stat_buf.st_mtime;
+ hotlist_state.modified = FALSE;
+ saved = TRUE;
+ }
+ }
+
+ return saved;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Unload list from memory.
+ * Don't confuse with hotlist_done() for GUI.
+ */
+
+void
+done_hotlist (void)
+{
+ if (hotlist != NULL)
+ {
+ remove_group (hotlist);
+ g_free (hotlist->label);
+ g_free (hotlist->directory);
+ MC_PTR_FREE (hotlist);
+ }
+
+ hotlist_state.loaded = FALSE;
+
+ MC_PTR_FREE (hotlist_file_name);
+ l_hotlist = NULL;
+ current_group = NULL;
+
+ if (tkn_buf != NULL)
+ {
+ g_string_free (tkn_buf, TRUE);
+ tkn_buf = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/hotlist.h b/src/filemanager/hotlist.h
new file mode 100644
index 0000000..94bc305
--- /dev/null
+++ b/src/filemanager/hotlist.h
@@ -0,0 +1,33 @@
+/** \file hotlist.h
+ * \brief Header: directory hotlist
+ */
+
+#ifndef MC__HOTLIST_H
+#define MC__HOTLIST_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#include "panel.h"
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ LIST_VFSLIST = 0x01,
+ LIST_HOTLIST = 0x02,
+ LIST_MOVELIST = 0x04
+} hotlist_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void add2hotlist_cmd (WPanel * panel);
+char *hotlist_show (hotlist_t list_type, WPanel * panel);
+gboolean save_hotlist (void);
+void done_hotlist (void);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__HOTLIST_H */
diff --git a/src/filemanager/info.c b/src/filemanager/info.c
new file mode 100644
index 0000000..790f820
--- /dev/null
+++ b/src/filemanager/info.c
@@ -0,0 +1,377 @@
+/*
+ Panel managing.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file info.c
+ * \brief Source: panel managing
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <inttypes.h> /* PRIuMAX */
+
+#include "lib/global.h"
+#include "lib/unixcompat.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* is_idle() */
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+
+#include "src/setup.h" /* panels_options */
+
+#include "filemanager.h" /* the_menubar */
+#include "layout.h"
+#include "mountlist.h"
+#ifdef ENABLE_EXT2FS_ATTR
+#include "cmd.h" /* chattr_get_as_str() */
+#endif
+
+#include "info.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+struct WInfo
+{
+ Widget widget;
+ gboolean ready;
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct my_statfs myfs_stats;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_box (WInfo * info)
+{
+ Widget *w = WIDGET (info);
+
+ const char *title = _("Information");
+ const int len = str_term_width1 (title);
+
+ tty_set_normal_attrs ();
+ tty_setcolor (NORMAL_COLOR);
+ widget_erase (w);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
+ tty_printf (" %s ", title);
+
+ widget_gotoyx (w, 2, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, 2, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ tty_draw_hline (w->rect.y + 2, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_show_info (WInfo * info)
+{
+ const WRect *w = &CONST_WIDGET (info)->rect;
+ const file_entry_t *fe;
+ static int i18n_adjust = 0;
+ static const char *file_label;
+ GString *buff;
+ struct stat st;
+ char rp_cwd[PATH_MAX];
+ const char *p_rp_cwd;
+
+ if (!is_idle ())
+ return;
+
+ info_box (info);
+
+ tty_setcolor (MARKED_COLOR);
+ widget_gotoyx (w, 1, 3);
+ tty_printf (_("Midnight Commander %s"), mc_global.mc_version);
+
+ if (!info->ready)
+ return;
+
+ if (get_current_type () != view_listing)
+ return;
+
+ /* don't rely on vpath CWD when cd_symlinks enabled */
+ p_rp_cwd = mc_realpath (vfs_path_as_str (current_panel->cwd_vpath), rp_cwd);
+ if (p_rp_cwd == NULL)
+ p_rp_cwd = vfs_path_as_str (current_panel->cwd_vpath);
+
+ my_statfs (&myfs_stats, p_rp_cwd);
+
+ fe = panel_current_entry (current_panel);
+
+ st = fe->st;
+
+ /* Print only lines which fit */
+
+ if (i18n_adjust == 0)
+ {
+ /* This printf pattern string is used as a reference for size */
+ file_label = _("File: %s");
+ i18n_adjust = str_term_width1 (file_label) + 2;
+ }
+
+ tty_setcolor (NORMAL_COLOR);
+
+ buff = g_string_new ("");
+
+ switch (w->lines - 2)
+ {
+ /* Note: all cases are fall-throughs */
+
+ default:
+ MC_FALLTHROUGH;
+ case 17:
+ widget_gotoyx (w, 17, 3);
+ if ((myfs_stats.nfree == 0 && myfs_stats.nodes == 0) ||
+ (myfs_stats.nfree == (uintmax_t) (-1) && myfs_stats.nodes == (uintmax_t) (-1)))
+ tty_print_string (_("No node information"));
+ else if (myfs_stats.nfree == (uintmax_t) (-1))
+ tty_printf ("%s - / %" PRIuMAX, _("Free nodes:"), myfs_stats.nodes);
+ else if (myfs_stats.nodes == (uintmax_t) (-1))
+ tty_printf ("%s %" PRIuMAX " / -", _("Free nodes:"), myfs_stats.nfree);
+ else
+ tty_printf ("%s %" PRIuMAX " / %" PRIuMAX " (%d%%)",
+ _("Free nodes:"),
+ myfs_stats.nfree, myfs_stats.nodes,
+ myfs_stats.nodes == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.nfree / myfs_stats.nodes));
+ MC_FALLTHROUGH;
+ case 16:
+ widget_gotoyx (w, 16, 3);
+ if (myfs_stats.avail == 0 && myfs_stats.total == 0)
+ tty_print_string (_("No space information"));
+ else
+ {
+ char buffer1[6], buffer2[6];
+
+ size_trunc_len (buffer1, 5, myfs_stats.avail, 1, panels_options.kilobyte_si);
+ size_trunc_len (buffer2, 5, myfs_stats.total, 1, panels_options.kilobyte_si);
+ tty_printf (_("Free space: %s / %s (%d%%)"), buffer1, buffer2,
+ myfs_stats.total == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.avail / myfs_stats.total));
+ }
+ MC_FALLTHROUGH;
+ case 15:
+ widget_gotoyx (w, 15, 3);
+ tty_printf (_("Type: %s"),
+ myfs_stats.typename ? myfs_stats.typename : _("non-local vfs"));
+ if (myfs_stats.type != 0xffff && myfs_stats.type != -1)
+ tty_printf (" (%Xh)", (unsigned int) myfs_stats.type);
+ MC_FALLTHROUGH;
+ case 14:
+ widget_gotoyx (w, 14, 3);
+ str_printf (buff, _("Device: %s"),
+ str_trunc (myfs_stats.device, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 13:
+ widget_gotoyx (w, 13, 3);
+ str_printf (buff, _("Filesystem: %s"),
+ str_trunc (myfs_stats.mpoint, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 12:
+ widget_gotoyx (w, 12, 3);
+ str_printf (buff, _("Accessed: %s"), file_date (st.st_atime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 11:
+ widget_gotoyx (w, 11, 3);
+ str_printf (buff, _("Modified: %s"), file_date (st.st_mtime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 10:
+ widget_gotoyx (w, 10, 3);
+ /* The field st_ctime is changed by writing or by setting inode
+ information (i.e., owner, group, link count, mode, etc.). */
+ /* TRANSLATORS: Time of last status change as in stat(2) man. */
+ str_printf (buff, _("Changed: %s"), file_date (st.st_ctime));
+ tty_print_string (buff->str);
+ g_string_set_size (buff, 0);
+ MC_FALLTHROUGH;
+ case 9:
+ widget_gotoyx (w, 9, 3);
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode))
+ tty_printf (_("Dev. type: major %lu, minor %lu"),
+ (unsigned long) major (st.st_rdev), (unsigned long) minor (st.st_rdev));
+ else
+#endif
+ {
+ char buffer[10];
+ size_trunc_len (buffer, 9, st.st_size, 0, panels_options.kilobyte_si);
+ tty_printf (_("Size: %s"), buffer);
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ tty_printf (ngettext (" (%lu block)", " (%lu blocks)",
+ (unsigned long) st.st_blocks), (unsigned long) st.st_blocks);
+#endif
+ }
+ MC_FALLTHROUGH;
+ case 8:
+ widget_gotoyx (w, 8, 3);
+ tty_printf (_("Owner: %s/%s"), get_owner (st.st_uid), get_group (st.st_gid));
+ MC_FALLTHROUGH;
+ case 7:
+ widget_gotoyx (w, 7, 3);
+ tty_printf (_("Links: %d"), (int) st.st_nlink);
+ MC_FALLTHROUGH;
+ case 6:
+ widget_gotoyx (w, 6, 3);
+
+ {
+ vfs_path_t *vpath;
+#ifdef ENABLE_EXT2FS_ATTR
+ unsigned long attr;
+#endif
+
+ vpath = vfs_path_from_str (fe->fname->str);
+
+#ifdef ENABLE_EXT2FS_ATTR
+ if (mc_fgetflags (vpath, &attr) == 0)
+ tty_printf (_("Attributes: %s"), chattr_get_as_str (attr));
+ else
+#endif
+ tty_print_string (_("Attributes: unavailable"));
+
+ vfs_path_free (vpath, TRUE);
+ }
+ MC_FALLTHROUGH;
+ case 5:
+ widget_gotoyx (w, 5, 3);
+ tty_printf (_("Mode: %s (%04o)"),
+ string_perm (st.st_mode), (unsigned) st.st_mode & 07777);
+ MC_FALLTHROUGH;
+ case 4:
+ widget_gotoyx (w, 4, 3);
+ tty_printf (_("Location: %Xh:%Xh"), (unsigned int) st.st_dev, (unsigned int) st.st_ino);
+ MC_FALLTHROUGH;
+ case 3:
+ {
+ const char *fname;
+
+ widget_gotoyx (w, 3, 2);
+ fname = fe->fname->str;
+ str_printf (buff, file_label, str_trunc (fname, w->cols - i18n_adjust));
+ tty_print_string (buff->str);
+ }
+ MC_FALLTHROUGH;
+ case 2:
+ MC_FALLTHROUGH;
+ case 1:
+ MC_FALLTHROUGH;
+ case 0:
+ ;
+ } /* switch */
+ g_string_free (buff, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+info_hook (void *data)
+{
+ WInfo *info = (WInfo *) data;
+ Widget *other_widget;
+
+ other_widget = get_panel_widget (get_current_index ());
+ if (!other_widget)
+ return;
+ if (widget_overlapped (WIDGET (info), other_widget))
+ return;
+
+ info->ready = TRUE;
+ info_show_info (info);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+info_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WInfo *info = (WInfo *) w;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ init_my_statfs ();
+ add_hook (&select_file_hook, info_hook, info);
+ info->ready = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ info_hook (info);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ delete_hook (&select_file_hook, info_hook);
+ free_my_statfs ();
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WInfo *
+info_new (int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+ WInfo *info;
+ Widget *w;
+
+ info = g_new (struct WInfo, 1);
+ w = WIDGET (info);
+ widget_init (w, &r, info_callback, NULL);
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/info.h b/src/filemanager/info.h
new file mode 100644
index 0000000..cba2592
--- /dev/null
+++ b/src/filemanager/info.h
@@ -0,0 +1,24 @@
+/** \file info.h
+ * \brief Header: panel managing
+ */
+
+#ifndef MC__INFO_H
+#define MC__INFO_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WInfo;
+typedef struct WInfo WInfo;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WInfo *info_new (int y, int x, int lines, int cols);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__INFO_H */
diff --git a/src/filemanager/ioblksize.h b/src/filemanager/ioblksize.h
new file mode 100644
index 0000000..91aa633
--- /dev/null
+++ b/src/filemanager/ioblksize.h
@@ -0,0 +1,86 @@
+/* I/O block size definitions for coreutils
+ Copyright (C) 1989-2016 Free Software Foundation, Inc.
+
+ 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 3 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, see <http://www.gnu.org/licenses/>. */
+
+/* Include this file _after_ system headers if possible. */
+
+/* sys/stat.h will already have been included by system.h. */
+#include "lib/stat-size.h"
+
+/* *INDENT-OFF* */
+
+/* As of May 2014, 128KiB is determined to be the minimum
+ blksize to best minimize system call overhead.
+ This can be tested with this script:
+
+ for i in $(seq 0 10); do
+ bs=$((1024*2**$i))
+ printf "%7s=" $bs
+ timeout --foreground -sINT 2 \
+ dd bs=$bs if=/dev/zero of=/dev/null 2>&1 \
+ | sed -n 's/.* \([0-9.]* [GM]B\/s\)/\1/p'
+ done
+
+ With the results shown for these systems:
+ system #1: 1.7GHz pentium-m with 400MHz DDR2 RAM, arch=i686
+ system #2: 2.1GHz i3-2310M with 1333MHz DDR3 RAM, arch=x86_64
+ system #3: 3.2GHz i7-970 with 1333MHz DDR3, arch=x86_64
+ system #4: 2.20GHz Xeon E5-2660 with 1333MHz DDR3, arch=x86_64
+ system #5: 2.30GHz i7-3615QM with 1600MHz DDR3, arch=x86_64
+ system #6: 1.30GHz i5-4250U with 1-channel 1600MHz DDR3, arch=x86_64
+ system #7: 3.55GHz IBM,8231-E2B with 1066MHz DDR3, POWER7 revision 2.1
+
+ per-system transfer rate (GB/s)
+ blksize #1 #2 #3 #4 #5 #6 #7
+ ------------------------------------------------------------------------
+ 1024 .73 1.7 2.6 .64 1.0 2.5 1.3
+ 2048 1.3 3.0 4.4 1.2 2.0 4.4 2.5
+ 4096 2.4 5.1 6.5 2.3 3.7 7.4 4.8
+ 8192 3.5 7.3 8.5 4.0 6.0 10.4 9.2
+ 16384 3.9 9.4 10.1 6.3 8.3 13.3 16.8
+ 32768 5.2 9.9 11.1 8.1 10.7 13.2 28.0
+ 65536 5.3 11.2 12.0 10.6 12.8 16.1 41.4
+ 131072 5.5 11.8 12.3 12.1 14.0 16.7 54.8
+ 262144 5.7 11.6 12.5 12.3 14.7 16.4 40.0
+ 524288 5.7 11.4 12.5 12.1 14.7 15.5 34.5
+ 1048576 5.8 11.4 12.6 12.2 14.9 15.7 36.5
+
+
+ Note that this is to minimize system call overhead.
+ Other values may be appropriate to minimize file system
+ or disk overhead. For example on my current GNU/Linux system
+ the readahead setting is 128KiB which was read using:
+
+ file="."
+ device=$(df --output=source --local "$file" | tail -n1)
+ echo $(( $(blockdev --getra $device) * 512 ))
+
+ However there isn't a portable way to get the above.
+ In the future we could use the above method if available
+ and default to io_blksize() if not.
+ */
+
+
+enum { IO_BUFSIZE = 128 * 1024 };
+
+/* *INDENT-ON* */
+
+static inline size_t
+io_blksize (struct stat sb)
+{
+ size_t blksize = ST_BLKSIZE (sb);
+
+ return MAX (IO_BUFSIZE, blksize);
+}
diff --git a/src/filemanager/layout.c b/src/filemanager/layout.c
new file mode 100644
index 0000000..c9d581f
--- /dev/null
+++ b/src/filemanager/layout.c
@@ -0,0 +1,1580 @@
+/*
+ Panel layout module for the Midnight Commander
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1995
+ Miguel de Icaza, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2011-2022
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Avi Kelman <patcherton.fixesthings@gmail.com>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file layout.c
+ * \brief Source: panel layout module
+ */
+
+#include <config.h>
+
+#include <pwd.h> /* for username in xterm title */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h"
+#include "lib/tty/mouse.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h" /* vfs_get_cwd () */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/event.h"
+#include "lib/util.h" /* mc_time_elapsed() */
+
+#include "src/consaver/cons.saver.h"
+#include "src/viewer/mcviewer.h" /* The view widget */
+#include "src/setup.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h"
+#endif
+
+#include "command.h"
+#include "filemanager.h"
+#include "tree.h"
+/* Needed for the extern declarations of integer parameters */
+#include "dir.h"
+#include "layout.h"
+#include "info.h" /* The Info widget */
+
+/*** global variables ****************************************************************************/
+
+panels_layout_t panels_layout = {
+ /* Set if the panels are split horizontally */
+ .horizontal_split = FALSE,
+
+ /* vertical split */
+ .vertical_equal = TRUE,
+ .left_panel_size = 0,
+
+ /* horizontal split */
+ .horizontal_equal = TRUE,
+ .top_panel_size = 0
+};
+
+/* Controls the display of the rotating dash on the verbose mode */
+gboolean nice_rotating_dash = TRUE;
+
+/* The number of output lines shown (if available) */
+int output_lines = 0;
+
+/* Set if the command prompt is to be displayed */
+gboolean command_prompt = TRUE;
+
+/* Set if the main menu is visible */
+gboolean menubar_visible = TRUE;
+
+/* Set to show current working dir in xterm window title */
+gboolean xterm_title = TRUE;
+
+/* Set to show free space on device assigned to current directory */
+gboolean free_space = TRUE;
+
+/* The starting line for the output of the subprogram */
+int output_start_y = 0;
+
+int ok_to_refresh = 1;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* The maximum number of views managed by the create_panel routine */
+/* Must be at least two (for current and other). Please note that until */
+/* Janne gets around this, we will only manage two of them :-) */
+#define MAX_VIEWS 2
+
+/* Width 12 for a wee Quick (Hex) View */
+#define MINWIDTH 12
+#define MINHEIGHT 5
+
+#define B_2LEFT B_USER
+#define B_2RIGHT (B_USER + 1)
+#define B_PLUS (B_USER + 2)
+#define B_MINUS (B_USER + 3)
+
+#define LAYOUT_OPTIONS_COUNT G_N_ELEMENTS (check_options)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ gboolean menubar_visible;
+ gboolean command_prompt;
+ gboolean keybar_visible;
+ gboolean message_visible;
+ gboolean xterm_title;
+ gboolean free_space;
+ int output_lines;
+} layout_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static struct
+{
+ panel_view_mode_t type;
+ Widget *widget;
+ char *last_saved_dir; /* last view_list working directory */
+} panels[MAX_VIEWS] =
+{
+ /* *INDENT-OFF* */
+ /* init MAX_VIEWS items */
+ { view_listing, NULL, NULL},
+ { view_listing, NULL, NULL}
+ /* *INDENT-ON* */
+};
+
+static layout_t old_layout;
+static panels_layout_t old_panels_layout;
+
+static gboolean equal_split;
+static int _output_lines;
+
+static int height;
+
+static WRadio *radio_widget;
+
+static struct
+{
+ const char *text;
+ gboolean *variable;
+ WCheck *widget;
+} check_options[] =
+{
+ /* *INDENT-OFF* */
+ { N_("&Equal split"), &equal_split, NULL },
+ { N_("&Menubar visible"), &menubar_visible, NULL },
+ { N_("Command &prompt"), &command_prompt, NULL },
+ { N_("&Keybar visible"), &mc_global.keybar_visible, NULL },
+ { N_("H&intbar visible"), &mc_global.message_visible, NULL },
+ { N_("&XTerm window title"), &xterm_title, NULL },
+ { N_("&Show free space"), &free_space, NULL }
+ /* *INDENT-ON* */
+};
+
+static const char *output_lines_label = NULL;
+static int output_lines_label_len;
+
+static WButton *bleft_widget, *bright_widget;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* don't use max() macro to avoid double call of str_term_width1() in widget width calculation */
+#undef max
+
+static int
+max (int a, int b)
+{
+ return a > b ? a : b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_split (panels_layout_t * layout)
+{
+ if (layout->horizontal_split)
+ {
+ if (layout->horizontal_equal)
+ layout->top_panel_size = height / 2;
+ else if (layout->top_panel_size < MINHEIGHT)
+ layout->top_panel_size = MINHEIGHT;
+ else if (layout->top_panel_size > height - MINHEIGHT)
+ layout->top_panel_size = height - MINHEIGHT;
+ }
+ else
+ {
+ int md_cols = CONST_WIDGET (filemanager)->rect.cols;
+
+ if (layout->vertical_equal)
+ layout->left_panel_size = md_cols / 2;
+ else if (layout->left_panel_size < MINWIDTH)
+ layout->left_panel_size = MINWIDTH;
+ else if (layout->left_panel_size > md_cols - MINWIDTH)
+ layout->left_panel_size = md_cols - MINWIDTH;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_split (const WDialog * h)
+{
+ /* Check split has to be done before testing if it changed, since
+ it can change due to calling check_split() as well */
+ check_split (&panels_layout);
+
+ if (panels_layout.horizontal_split)
+ check_options[0].widget->state = panels_layout.horizontal_equal;
+ else
+ check_options[0].widget->state = panels_layout.vertical_equal;
+ widget_draw (WIDGET (check_options[0].widget));
+
+ tty_setcolor (check_options[0].widget->state ? DISABLED_COLOR : COLOR_NORMAL);
+
+ widget_gotoyx (h, 6, 5);
+ if (panels_layout.horizontal_split)
+ tty_printf ("%03d", panels_layout.top_panel_size);
+ else
+ tty_printf ("%03d", panels_layout.left_panel_size);
+
+ widget_gotoyx (h, 6, 17);
+ if (panels_layout.horizontal_split)
+ tty_printf ("%03d", height - panels_layout.top_panel_size);
+ else
+ tty_printf ("%03d", CONST_WIDGET (filemanager)->rect.cols - panels_layout.left_panel_size);
+
+ widget_gotoyx (h, 6, 12);
+ tty_print_char ('=');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+b_left_right_cback (WButton * button, int action)
+{
+ (void) action;
+
+ if (button == bright_widget)
+ {
+ if (panels_layout.horizontal_split)
+ panels_layout.top_panel_size++;
+ else
+ panels_layout.left_panel_size++;
+ }
+ else
+ {
+ if (panels_layout.horizontal_split)
+ panels_layout.top_panel_size--;
+ else
+ panels_layout.left_panel_size--;
+ }
+
+ update_split (DIALOG (WIDGET (button)->owner));
+ layout_change ();
+ do_refresh ();
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+bplus_cback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ if (_output_lines < 99)
+ _output_lines++;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+bminus_cback (WButton * button, int action)
+{
+ (void) button;
+ (void) action;
+
+ if (_output_lines > 0)
+ _output_lines--;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+layout_bg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_DRAW:
+ frame_callback (w, NULL, MSG_DRAW, 0, NULL);
+
+ old_layout.output_lines = -1;
+
+ update_split (DIALOG (w->owner));
+
+ if (old_layout.output_lines != _output_lines)
+ {
+ old_layout.output_lines = _output_lines;
+ tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR);
+ widget_gotoyx (w, 9, 5);
+ tty_print_string (output_lines_label);
+ widget_gotoyx (w, 9, 5 + 3 + output_lines_label_len);
+ tty_printf ("%02d", _output_lines);
+ }
+ return MSG_HANDLED;
+
+ default:
+ return frame_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+layout_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_POST_KEY:
+ {
+ const Widget *mw = CONST_WIDGET (filemanager);
+ gboolean _menubar_visible, _command_prompt, _keybar_visible, _message_visible;
+
+ _menubar_visible = check_options[1].widget->state;
+ _command_prompt = check_options[2].widget->state;
+ _keybar_visible = check_options[3].widget->state;
+ _message_visible = check_options[4].widget->state;
+
+ if (mc_global.tty.console_flag == '\0')
+ height =
+ mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) -
+ (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0);
+ else
+ {
+ int minimum;
+
+ if (_output_lines < 0)
+ _output_lines = 0;
+ height =
+ mw->rect.lines - (_keybar_visible ? 1 : 0) - (_command_prompt ? 1 : 0) -
+ (_menubar_visible ? 1 : 0) - _output_lines - (_message_visible ? 1 : 0);
+ minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0));
+ if (height < minimum)
+ {
+ _output_lines -= minimum - height;
+ height = minimum;
+ }
+ }
+
+ if (old_layout.output_lines != _output_lines)
+ {
+ old_layout.output_lines = _output_lines;
+ tty_setcolor (mc_global.tty.console_flag != '\0' ? COLOR_NORMAL : DISABLED_COLOR);
+ widget_gotoyx (h, 9, 5 + 3 + output_lines_label_len);
+ tty_printf ("%02d", _output_lines);
+ }
+ }
+ return MSG_HANDLED;
+
+ case MSG_NOTIFY:
+ if (sender == WIDGET (radio_widget))
+ {
+ if ((panels_layout.horizontal_split ? 1 : 0) == radio_widget->sel)
+ update_split (h);
+ else
+ {
+ int eq;
+
+ panels_layout.horizontal_split = radio_widget->sel != 0;
+
+ if (panels_layout.horizontal_split)
+ {
+ eq = panels_layout.horizontal_equal;
+ if (eq)
+ panels_layout.top_panel_size = height / 2;
+ }
+ else
+ {
+ eq = panels_layout.vertical_equal;
+ if (eq)
+ panels_layout.left_panel_size = CONST_WIDGET (filemanager)->rect.cols / 2;
+ }
+
+ widget_disable (WIDGET (bleft_widget), eq);
+ widget_disable (WIDGET (bright_widget), eq);
+
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+ }
+
+ return MSG_HANDLED;
+ }
+
+ if (sender == WIDGET (check_options[0].widget))
+ {
+ gboolean eq;
+
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = check_options[0].widget->state;
+ eq = panels_layout.horizontal_equal;
+ }
+ else
+ {
+ panels_layout.vertical_equal = check_options[0].widget->state;
+ eq = panels_layout.vertical_equal;
+ }
+
+ widget_disable (WIDGET (bleft_widget), eq);
+ widget_disable (WIDGET (bright_widget), eq);
+
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+
+ return MSG_HANDLED;
+ }
+
+ {
+ gboolean ok = TRUE;
+
+ if (sender == WIDGET (check_options[1].widget))
+ menubar_visible = check_options[1].widget->state;
+ else if (sender == WIDGET (check_options[2].widget))
+ command_prompt = check_options[2].widget->state;
+ else if (sender == WIDGET (check_options[3].widget))
+ mc_global.keybar_visible = check_options[3].widget->state;
+ else if (sender == WIDGET (check_options[4].widget))
+ mc_global.message_visible = check_options[4].widget->state;
+ else if (sender == WIDGET (check_options[5].widget))
+ xterm_title = check_options[5].widget->state;
+ else if (sender == WIDGET (check_options[6].widget))
+ free_space = check_options[6].widget->state;
+ else
+ ok = FALSE;
+
+ if (ok)
+ {
+ update_split (h);
+ layout_change ();
+ do_refresh ();
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static WDialog *
+layout_dlg_create (void)
+{
+ WDialog *layout_dlg;
+ WGroup *g;
+ int l1 = 0, width;
+ int b1, b2, b;
+ size_t i;
+
+ const char *title1 = N_("Panel split");
+ const char *title2 = N_("Console output");
+ const char *title3 = N_("Other options");
+
+ const char *s_split_direction[2] = {
+ N_("&Vertical"),
+ N_("&Horizontal")
+ };
+
+ const char *ok_button = N_("&OK");
+ const char *cancel_button = N_("&Cancel");
+
+ output_lines_label = _("Output lines:");
+
+#ifdef ENABLE_NLS
+ {
+ static gboolean i18n = FALSE;
+
+ title1 = _(title1);
+ title2 = _(title2);
+ title3 = _(title3);
+
+ i = G_N_ELEMENTS (s_split_direction);
+ while (i-- != 0)
+ s_split_direction[i] = _(s_split_direction[i]);
+
+ if (!i18n)
+ {
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ check_options[i].text = _(check_options[i].text);
+ i18n = TRUE;
+ }
+
+ ok_button = _(ok_button);
+ cancel_button = _(cancel_button);
+ }
+#endif
+
+ /* radiobuttons */
+ i = G_N_ELEMENTS (s_split_direction);
+ while (i-- != 0)
+ l1 = max (l1, str_term_width1 (s_split_direction[i]) + 7);
+ /* checkboxes */
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ l1 = max (l1, str_term_width1 (check_options[i].text) + 7);
+ /* groupboxes */
+ l1 = max (l1, str_term_width1 (title1) + 4);
+ l1 = max (l1, str_term_width1 (title2) + 4);
+ l1 = max (l1, str_term_width1 (title3) + 4);
+ /* label + "+"/"-" buttons */
+ output_lines_label_len = str_term_width1 (output_lines_label);
+ l1 = max (l1, output_lines_label_len + 12);
+ /* buttons */
+ b1 = str_term_width1 (ok_button) + 5; /* default button */
+ b2 = str_term_width1 (cancel_button) + 3;
+ b = b1 + b2 + 1;
+ /* dialog width */
+ width = max (l1 * 2 + 7, b);
+
+ layout_dlg =
+ dlg_create (TRUE, 0, 0, 15, width, WPOS_CENTER, FALSE, dialog_colors, layout_callback, NULL,
+ "[Layout]", _("Layout"));
+ g = GROUP (layout_dlg);
+
+ /* draw background */
+ layout_dlg->bg->callback = layout_bg_callback;
+
+#define XTRACT(i) (*check_options[i].variable != 0), check_options[i].text
+
+ /* "Panel split" groupbox */
+ group_add_widget (g, groupbox_new (2, 3, 6, l1, title1));
+
+ radio_widget = radio_new (3, 5, 2, s_split_direction);
+ radio_widget->sel = panels_layout.horizontal_split ? 1 : 0;
+ group_add_widget (g, radio_widget);
+
+ check_options[0].widget = check_new (5, 5, XTRACT (0));
+ group_add_widget (g, check_options[0].widget);
+
+ equal_split = panels_layout.horizontal_split ?
+ panels_layout.horizontal_equal : panels_layout.vertical_equal;
+
+ bleft_widget = button_new (6, 8, B_2LEFT, NARROW_BUTTON, "&<", b_left_right_cback);
+ widget_disable (WIDGET (bleft_widget), equal_split);
+ group_add_widget (g, bleft_widget);
+
+ bright_widget = button_new (6, 14, B_2RIGHT, NARROW_BUTTON, "&>", b_left_right_cback);
+ widget_disable (WIDGET (bright_widget), equal_split);
+ group_add_widget (g, bright_widget);
+
+ /* "Console output" groupbox */
+ {
+ widget_state_t disabled;
+ Widget *w;
+
+ disabled = mc_global.tty.console_flag != '\0' ? 0 : WST_DISABLED;
+
+ w = WIDGET (groupbox_new (8, 3, 3, l1, title2));
+ w->state |= disabled;
+ group_add_widget (g, w);
+
+ w = WIDGET (button_new (9, output_lines_label_len + 5, B_PLUS,
+ NARROW_BUTTON, "&+", bplus_cback));
+ w->state |= disabled;
+ group_add_widget (g, w);
+
+ w = WIDGET (button_new (9, output_lines_label_len + 5 + 5, B_MINUS,
+ NARROW_BUTTON, "&-", bminus_cback));
+ w->state |= disabled;
+ group_add_widget (g, w);
+ }
+
+ /* "Other options" groupbox */
+ group_add_widget (g, groupbox_new (2, 4 + l1, 9, l1, title3));
+
+ for (i = 1; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ {
+ check_options[i].widget = check_new (i + 2, 6 + l1, XTRACT (i));
+ group_add_widget (g, check_options[i].widget);
+ }
+
+#undef XTRACT
+
+ group_add_widget (g, hline_new (11, -1, -1));
+ /* buttons */
+ group_add_widget (g, button_new (12, (width - b) / 2, B_ENTER, DEFPUSH_BUTTON, ok_button, 0));
+ group_add_widget (g,
+ button_new (12, (width - b) / 2 + b1 + 1, B_CANCEL, NORMAL_BUTTON,
+ cancel_button, 0));
+
+ widget_select (WIDGET (radio_widget));
+
+ return layout_dlg;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_do_cols (int idx)
+{
+ if (get_panel_type (idx) == view_listing)
+ set_panel_formats (PANEL (panels[idx].widget));
+ else
+ panel_update_cols (panels[idx].widget, frame_half);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save current list_view widget directory into panel */
+
+static Widget *
+restore_into_right_dir_panel (int idx, gboolean last_was_panel, int y, int x, int lines, int cols)
+{
+ WPanel *new_widget;
+ const char *p_name;
+
+ p_name = get_nth_panel_name (idx);
+
+ if (last_was_panel)
+ {
+ vfs_path_t *saved_dir_vpath;
+
+ saved_dir_vpath = vfs_path_from_str (panels[idx].last_saved_dir);
+ new_widget = panel_sized_with_dir_new (p_name, y, x, lines, cols, saved_dir_vpath);
+ vfs_path_free (saved_dir_vpath, TRUE);
+ }
+ else
+ new_widget = panel_sized_new (p_name, y, x, lines, cols);
+
+ return WIDGET (new_widget);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+layout_save (void)
+{
+ old_layout.menubar_visible = menubar_visible;
+ old_layout.command_prompt = command_prompt;
+ old_layout.keybar_visible = mc_global.keybar_visible;
+ old_layout.message_visible = mc_global.message_visible;
+ old_layout.xterm_title = xterm_title;
+ old_layout.free_space = free_space;
+ old_layout.output_lines = -1;
+
+ _output_lines = output_lines;
+
+ old_panels_layout = panels_layout;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+layout_restore (void)
+{
+ menubar_visible = old_layout.menubar_visible;
+ command_prompt = old_layout.command_prompt;
+ mc_global.keybar_visible = old_layout.keybar_visible;
+ mc_global.message_visible = old_layout.message_visible;
+ xterm_title = old_layout.xterm_title;
+ free_space = old_layout.free_space;
+ output_lines = old_layout.output_lines;
+
+ panels_layout = old_panels_layout;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+layout_change (void)
+{
+ setup_panels ();
+ /* update the main menu, because perhaps there was a change in the way
+ how the panel are split (horizontal/vertical),
+ and a change of menu visibility. */
+ update_menu ();
+ load_hint (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+layout_box (void)
+{
+ WDialog *layout_dlg;
+
+ layout_save ();
+
+ layout_dlg = layout_dlg_create ();
+
+ if (dlg_run (layout_dlg) == B_ENTER)
+ {
+ size_t i;
+
+ for (i = 0; i < (size_t) LAYOUT_OPTIONS_COUNT; i++)
+ if (check_options[i].widget != NULL)
+ *check_options[i].variable = check_options[i].widget->state;
+
+ output_lines = _output_lines;
+ }
+ else
+ layout_restore ();
+
+ widget_destroy (WIDGET (layout_dlg));
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_update_cols (Widget * widget, panel_display_t frame_size)
+{
+ const Widget *mw = CONST_WIDGET (filemanager);
+ int cols, x;
+
+ /* don't touch panel if it is not in dialog yet */
+ /* if panel is not in dialog it is not in widgets list
+ and cannot be compared with get_panel_widget() result */
+ if (widget->owner == NULL)
+ return;
+
+ if (panels_layout.horizontal_split)
+ {
+ widget->rect.cols = mw->rect.cols;
+ return;
+ }
+
+ if (frame_size == frame_full)
+ {
+ cols = mw->rect.cols;
+ x = mw->rect.x;
+ }
+ else if (widget == get_panel_widget (0))
+ {
+ cols = panels_layout.left_panel_size;
+ x = mw->rect.x;
+ }
+ else
+ {
+ cols = mw->rect.cols - panels_layout.left_panel_size;
+ x = mw->rect.x + panels_layout.left_panel_size;
+ }
+
+ widget->rect.cols = cols;
+ widget->rect.x = x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_panels (void)
+{
+ /* File manager screen layout:
+ *
+ * +---------------------------------------------------------------+
+ * | Menu bar |
+ * +-------------------------------+-------------------------------+
+ * | | |
+ * | | |
+ * | | |
+ * | | |
+ * | Left panel | Right panel |
+ * | | |
+ * | | |
+ * | | |
+ * | | |
+ * +-------------------------------+-------------------------------+
+ * | Hint (message) bar |
+ * +---------------------------------------------------------------+
+ * | |
+ * | Console content |
+ * | |
+ * +--------+------------------------------------------------------+
+ * | Prompt | Command line |
+ * | Key (button) bar |
+ * +--------+------------------------------------------------------+
+ */
+
+ Widget *mw = WIDGET (filemanager);
+ const WRect *r = &CONST_WIDGET (mw)->rect;
+ int start_y;
+ gboolean active;
+ WRect rb;
+
+ active = widget_get_state (mw, WST_ACTIVE);
+
+ /* lock the group to avoid many redraws */
+ if (active)
+ widget_set_state (mw, WST_SUSPENDED, TRUE);
+
+ /* initial height of panels */
+ height =
+ r->lines - (menubar_visible ? 1 : 0) - (mc_global.message_visible ? 1 : 0) -
+ (command_prompt ? 1 : 0) - (mc_global.keybar_visible ? 1 : 0);
+
+ if (mc_global.tty.console_flag != '\0')
+ {
+ int minimum;
+
+ if (output_lines < 0)
+ output_lines = 0;
+ else
+ height -= output_lines;
+ minimum = MINHEIGHT * (1 + (panels_layout.horizontal_split ? 1 : 0));
+ if (height < minimum)
+ {
+ output_lines -= minimum - height;
+ height = minimum;
+ }
+ }
+
+ rb = *r;
+ rb.lines = 1;
+ widget_set_size_rect (WIDGET (the_menubar), &rb);
+ widget_set_visibility (WIDGET (the_menubar), menubar_visible);
+
+ check_split (&panels_layout);
+ start_y = r->y + (menubar_visible ? 1 : 0);
+
+ /* update columns first... */
+ panel_do_cols (0);
+ panel_do_cols (1);
+
+ /* ...then rows and origin */
+ if (panels_layout.horizontal_split)
+ {
+ widget_set_size (panels[0].widget, start_y, r->x, panels_layout.top_panel_size,
+ panels[0].widget->rect.cols);
+ widget_set_size (panels[1].widget, start_y + panels_layout.top_panel_size, r->x,
+ height - panels_layout.top_panel_size, panels[1].widget->rect.cols);
+ }
+ else
+ {
+ widget_set_size (panels[0].widget, start_y, r->x, height, panels[0].widget->rect.cols);
+ widget_set_size (panels[1].widget, start_y, panels[1].widget->rect.x, height,
+ panels[1].widget->rect.cols);
+ }
+
+ widget_set_size (WIDGET (the_hint), height + start_y, r->x, 1, r->cols);
+ widget_set_visibility (WIDGET (the_hint), mc_global.message_visible);
+
+ /* Output window */
+ if (mc_global.tty.console_flag != '\0' && output_lines != 0)
+ {
+ unsigned char end_line;
+
+ end_line = r->lines - (mc_global.keybar_visible ? 1 : 0) - 1;
+ output_start_y = end_line - (command_prompt ? 1 : 0) - output_lines + 1;
+ show_console_contents (output_start_y, end_line - output_lines, end_line);
+ }
+
+ if (command_prompt)
+ {
+#ifdef ENABLE_SUBSHELL
+ if (!mc_global.tty.use_subshell || !do_load_prompt ())
+#endif
+ setup_cmdline ();
+ }
+ else
+ {
+ /* make invisible */
+ widget_hide (WIDGET (cmdline));
+ widget_hide (WIDGET (the_prompt));
+ }
+
+ rb = *r;
+ rb.y = r->lines - 1;
+ rb.lines = 1;
+ widget_set_size_rect (WIDGET (the_bar), &rb);
+ widget_set_visibility (WIDGET (the_bar), mc_global.keybar_visible);
+
+ update_xterm_title_path ();
+
+ /* unlock */
+ if (active)
+ {
+ widget_set_state (mw, WST_ACTIVE, TRUE);
+ widget_draw (mw);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_equal (void)
+{
+ if (panels_layout.horizontal_split)
+ panels_layout.horizontal_equal = TRUE;
+ else
+ panels_layout.vertical_equal = TRUE;
+
+ layout_change ();
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_more (void)
+{
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = FALSE;
+ panels_layout.top_panel_size++;
+ }
+ else
+ {
+ panels_layout.vertical_equal = FALSE;
+ panels_layout.left_panel_size++;
+ }
+
+ layout_change ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panels_split_less (void)
+{
+ if (panels_layout.horizontal_split)
+ {
+ panels_layout.horizontal_equal = FALSE;
+ panels_layout.top_panel_size--;
+ }
+ else
+ {
+ panels_layout.vertical_equal = FALSE;
+ panels_layout.left_panel_size--;
+ }
+
+ layout_change ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+setup_cmdline (void)
+{
+ const Widget *mw = CONST_WIDGET (filemanager);
+ const WRect *r = &mw->rect;
+ int prompt_width;
+ int y;
+ char *tmp_prompt = (char *) mc_prompt;
+
+ if (!command_prompt)
+ return;
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ /* Workaround: avoid crash on FreeBSD (see ticket #4213 for details) */
+ if (subshell_prompt != NULL)
+ tmp_prompt = g_string_free (subshell_prompt, FALSE);
+ else
+ tmp_prompt = g_strdup (mc_prompt);
+ (void) strip_ctrl_codes (tmp_prompt);
+ }
+#endif
+
+ prompt_width = str_term_width1 (tmp_prompt);
+
+ /* Check for prompts too big */
+ if (r->cols > 8 && prompt_width > r->cols - 8)
+ {
+ int prompt_len;
+
+ prompt_width = r->cols - 8;
+ prompt_len = str_offset_to_pos (tmp_prompt, prompt_width);
+ tmp_prompt[prompt_len] = '\0';
+ }
+
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell)
+ {
+ subshell_prompt = g_string_new (tmp_prompt);
+ g_free (tmp_prompt);
+ mc_prompt = subshell_prompt->str;
+ }
+#endif
+
+ y = r->lines - 1 - (mc_global.keybar_visible ? 1 : 0);
+
+ widget_set_size (WIDGET (the_prompt), y, r->x, 1, prompt_width);
+ label_set_text (the_prompt, mc_prompt);
+ widget_set_size (WIDGET (cmdline), y, r->x + prompt_width, 1, r->cols - prompt_width);
+
+ widget_show (WIDGET (the_prompt));
+ widget_show (WIDGET (cmdline));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+use_dash (gboolean flag)
+{
+ if (flag)
+ ok_to_refresh++;
+ else
+ ok_to_refresh--;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+set_hintbar (const char *str)
+{
+ label_set_text (the_hint, str);
+ if (ok_to_refresh > 0)
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+rotate_dash (gboolean show)
+{
+ static gint64 timestamp = 0;
+ /* update with 10 FPS rate */
+ static const gint64 delay = G_USEC_PER_SEC / 10;
+
+ const Widget *w = CONST_WIDGET (filemanager);
+
+ if (!nice_rotating_dash || (ok_to_refresh <= 0))
+ return;
+
+ if (show && !mc_time_elapsed (&timestamp, delay))
+ return;
+
+ widget_gotoyx (w, menubar_visible ? 1 : 0, w->rect.cols - 1);
+ tty_setcolor (NORMAL_COLOR);
+
+ if (!show)
+ tty_print_alt_char (ACS_URCORNER, FALSE);
+ else
+ {
+ static const char rotating_dash[4] = "|/-\\";
+ static size_t pos = 0;
+
+ tty_print_char (rotating_dash[pos]);
+ pos = (pos + 1) % sizeof (rotating_dash);
+ }
+
+ mc_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const char *
+get_nth_panel_name (int num)
+{
+ if (num == 0)
+ return "New Left Panel";
+
+ if (num == 1)
+ return "New Right Panel";
+
+ {
+ static char buffer[BUF_SMALL];
+
+ g_snprintf (buffer, sizeof (buffer), "%ith Panel", num);
+ return buffer;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* I wonder if I should start to use the folding mode than Dugan uses */
+/* */
+/* This is the centralized managing of the panel display types */
+/* This routine takes care of destroying and creating new widgets */
+/* Please note that it could manage MAX_VIEWS, not just left and right */
+/* Currently nothing in the code takes advantage of this and has hard- */
+/* coded values for two panels only */
+
+/* Set the num-th panel to the view type: type */
+/* This routine also keeps at least one WPanel object in the screen */
+/* since a lot of routines depend on the current_panel variable */
+
+void
+create_panel (int num, panel_view_mode_t type)
+{
+ WRect r = { 0, 0, 0, 0 };
+ unsigned int the_other = 0; /* Index to the other panel */
+ const char *file_name = NULL; /* For Quick view */
+ Widget *new_widget = NULL, *old_widget = NULL;
+ panel_view_mode_t old_type = view_listing;
+ WPanel *the_other_panel = NULL;
+
+ if (num >= MAX_VIEWS)
+ {
+ fprintf (stderr, "Cannot allocate more that %d views\n", MAX_VIEWS);
+ abort ();
+ }
+ /* Check that we will have a WPanel * at least */
+ if (type != view_listing)
+ {
+ the_other = num == 0 ? 1 : 0;
+
+ if (panels[the_other].type != view_listing)
+ return;
+ }
+
+ /* Get rid of it */
+ if (panels[num].widget != NULL)
+ {
+ Widget *w = panels[num].widget;
+ WPanel *panel = PANEL (w);
+
+ r = w->rect;
+ old_widget = w;
+ old_type = panels[num].type;
+
+ if (old_type == view_listing && panel->frame_size == frame_full && type != view_listing)
+ {
+ int md_cols = CONST_WIDGET (filemanager)->rect.cols;
+
+ if (panels_layout.horizontal_split)
+ {
+ r.cols = md_cols;
+ r.x = 0;
+ }
+ else
+ {
+ r.cols = md_cols - panels_layout.left_panel_size;
+ if (num == 1)
+ r.x = panels_layout.left_panel_size;
+ }
+ }
+ }
+
+ /* Restoring saved path from panels.ini for nonlist panel */
+ /* when it's first creation (for example view_info) */
+ if (old_widget == NULL && type != view_listing)
+ panels[num].last_saved_dir = vfs_get_cwd ();
+
+ switch (type)
+ {
+ case view_nothing:
+ case view_listing:
+ {
+ gboolean last_was_panel;
+
+ last_was_panel = old_widget != NULL && get_panel_type (num) != view_listing;
+ new_widget =
+ restore_into_right_dir_panel (num, last_was_panel, r.y, r.x, r.lines, r.cols);
+ break;
+ }
+
+ case view_info:
+ new_widget = WIDGET (info_new (r.y, r.x, r.lines, r.cols));
+ break;
+
+ case view_tree:
+ new_widget = WIDGET (tree_new (r.y, r.x, r.lines, r.cols, TRUE));
+ break;
+
+ case view_quick:
+ new_widget = WIDGET (mcview_new (r.y, r.x, r.lines, r.cols, TRUE));
+ the_other_panel = PANEL (panels[the_other].widget);
+ if (the_other_panel != NULL)
+ file_name = panel_current_entry (the_other_panel)->fname->str;
+ else
+ file_name = "";
+
+ mcview_load ((WView *) new_widget, 0, file_name, 0, 0, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ if (type != view_listing)
+ /* Must save dir, for restoring after change type to */
+ /* view_listing */
+ save_panel_dir (num);
+
+ panels[num].type = type;
+ panels[num].widget = new_widget;
+
+ /* We use replace to keep the circular list of the dialog in the */
+ /* same state. Maybe we could just kill it and then replace it */
+ if (old_widget != NULL)
+ {
+ if (old_type == view_listing)
+ {
+ /* save and write directory history of panel
+ * ... and other histories of filemanager */
+ dlg_save_history (filemanager);
+ }
+
+ widget_replace (old_widget, new_widget);
+ }
+
+ if (type == view_listing)
+ {
+ WPanel *panel = PANEL (new_widget);
+
+ /* if existing panel changed type to view_listing, then load history */
+ if (old_widget != NULL)
+ {
+ ev_history_load_save_t event_data = { NULL, new_widget };
+
+ mc_event_raise (filemanager->event_group, MCEVENT_HISTORY_LOAD, &event_data);
+ }
+
+ if (num == 0)
+ left_panel = panel;
+ else
+ right_panel = panel;
+
+ /* forced update format after set new sizes */
+ set_panel_formats (panel);
+ }
+
+ if (type == view_tree)
+ the_tree = (WTree *) new_widget;
+
+ /* Prevent current_panel's value from becoming invalid.
+ * It's just a quick hack to prevent segfaults. Comment out and
+ * try following:
+ * - select left panel
+ * - invoke menu left/tree
+ * - as long as you stay in the left panel almost everything that uses
+ * current_panel causes segfault, e.g. C-Enter, C-x c, ...
+ */
+ if ((type != view_listing) && (current_panel == PANEL (old_widget)))
+ current_panel = num == 0 ? right_panel : left_panel;
+
+ g_free (old_widget);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This routine is deeply sticked to the two panels idea.
+ What should it do in more panels. ANSWER - don't use it
+ in any multiple panels environment. */
+
+void
+swap_panels (void)
+{
+ WPanel *panel1, *panel2;
+ Widget *tmp_widget;
+
+ panel1 = PANEL (panels[0].widget);
+ panel2 = PANEL (panels[1].widget);
+
+ if (panels[0].type == view_listing && panels[1].type == view_listing &&
+ !mc_config_get_bool (mc_global.main_config, CONFIG_PANELS_SECTION, "simple_swap", FALSE))
+ {
+ WPanel panel;
+
+#define panelswap(x) panel.x = panel1->x; panel1->x = panel2->x; panel2->x = panel.x;
+ /* Change content and related stuff */
+ panelswap (dir);
+ panelswap (active);
+ panelswap (cwd_vpath);
+ panelswap (lwd_vpath);
+ panelswap (marked);
+ panelswap (dirs_marked);
+ panelswap (total);
+ panelswap (top);
+ panelswap (current);
+ panelswap (is_panelized);
+ panelswap (panelized_descr);
+ panelswap (dir_stat);
+#undef panelswap
+
+ panel1->quick_search.active = FALSE;
+ panel2->quick_search.active = FALSE;
+
+ if (current_panel == panel1)
+ current_panel = panel2;
+ else
+ current_panel = panel1;
+
+ /* if sort options are different -> resort panels */
+ if (memcmp (&panel1->sort_info, &panel2->sort_info, sizeof (dir_sort_options_t)) != 0)
+ {
+ panel_re_sort (other_panel);
+ panel_re_sort (current_panel);
+ }
+
+ if (widget_is_active (panels[0].widget))
+ widget_select (panels[1].widget);
+ else if (widget_is_active (panels[1].widget))
+ widget_select (panels[0].widget);
+ }
+ else
+ {
+ WPanel *tmp_panel;
+ WRect r;
+ int tmp_type;
+
+ tmp_panel = right_panel;
+ right_panel = left_panel;
+ left_panel = tmp_panel;
+
+ if (panels[0].type == view_listing)
+ {
+ if (strcmp (panel1->name, get_nth_panel_name (0)) == 0)
+ {
+ g_free (panel1->name);
+ panel1->name = g_strdup (get_nth_panel_name (1));
+ }
+ }
+ if (panels[1].type == view_listing)
+ {
+ if (strcmp (panel2->name, get_nth_panel_name (1)) == 0)
+ {
+ g_free (panel2->name);
+ panel2->name = g_strdup (get_nth_panel_name (0));
+ }
+ }
+
+ r = panels[0].widget->rect;
+ panels[0].widget->rect = panels[1].widget->rect;
+ panels[1].widget->rect = r;
+
+ tmp_widget = panels[0].widget;
+ panels[0].widget = panels[1].widget;
+ panels[1].widget = tmp_widget;
+ tmp_type = panels[0].type;
+ panels[0].type = panels[1].type;
+ panels[1].type = tmp_type;
+
+ /* force update formats because of possible changed sizes */
+ if (panels[0].type == view_listing)
+ set_panel_formats (PANEL (panels[0].widget));
+ if (panels[1].type == view_listing)
+ set_panel_formats (PANEL (panels[1].widget));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+panel_view_mode_t
+get_panel_type (int idx)
+{
+ return panels[idx].type;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+Widget *
+get_panel_widget (int idx)
+{
+ return panels[idx].widget;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_current_index (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? 0 : 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+get_other_index (void)
+{
+ return (get_current_index () == 0 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WPanel *
+get_other_panel (void)
+{
+ return PANEL (get_panel_widget (get_other_index ()));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the view type for the current panel/view */
+
+panel_view_mode_t
+get_current_type (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? panels[0].type : panels[1].type);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the view type of the unselected panel */
+
+panel_view_mode_t
+get_other_type (void)
+{
+ return (panels[0].widget == WIDGET (current_panel) ? panels[1].type : panels[0].type);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save current list_view widget directory into panel */
+
+void
+save_panel_dir (int idx)
+{
+ panel_view_mode_t type;
+
+ type = get_panel_type (idx);
+ if (type == view_listing)
+ {
+ WPanel *p;
+
+ p = PANEL (get_panel_widget (idx));
+ if (p != NULL)
+ {
+ g_free (panels[idx].last_saved_dir); /* last path no needed */
+ /* Because path can be nonlocal */
+ panels[idx].last_saved_dir = g_strdup (vfs_path_as_str (p->cwd_vpath));
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return working dir, if it's view_listing - cwd,
+ but for other types - last_saved_dir */
+
+char *
+get_panel_dir_for (const WPanel * widget)
+{
+ int i;
+
+ for (i = 0; i < MAX_VIEWS; i++)
+ if (PANEL (get_panel_widget (i)) == widget)
+ break;
+
+ if (i >= MAX_VIEWS)
+ return g_strdup (".");
+
+ if (get_panel_type (i) == view_listing)
+ {
+ vfs_path_t *cwd_vpath;
+
+ cwd_vpath = PANEL (get_panel_widget (i))->cwd_vpath;
+ return g_strdup (vfs_path_as_str (cwd_vpath));
+ }
+
+ return g_strdup (panels[i].last_saved_dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_SUBSHELL
+gboolean
+do_load_prompt (void)
+{
+ gboolean ret = FALSE;
+
+ if (!read_subshell_prompt ())
+ return ret;
+
+ /* Don't actually change the prompt if it's invisible */
+ if (top_dlg != NULL && DIALOG (top_dlg->data) == filemanager && command_prompt)
+ {
+ setup_cmdline ();
+
+ /* since the prompt has changed, and we are called from one of the
+ * tty_get_event channels, the prompt updating does not take place
+ * automatically: force a cursor update and a screen refresh
+ */
+ widget_update_cursor (WIDGET (filemanager));
+ mc_refresh ();
+ ret = TRUE;
+ }
+ update_subshell_prompt = TRUE;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+load_prompt (int fd, void *unused)
+{
+ (void) fd;
+ (void) unused;
+
+ if (should_read_new_subshell_prompt)
+ do_load_prompt ();
+ else
+ flush_subshell (0, QUIETLY);
+
+ return 0;
+}
+#endif /* ENABLE_SUBSHELL */
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+title_path_prepare (char **path, char **login)
+{
+ char host[BUF_TINY];
+ struct passwd *pw = NULL;
+ int res = 0;
+
+ *path =
+ vfs_path_to_str_flags (current_panel->cwd_vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
+
+ res = gethostname (host, sizeof (host));
+ if (res != 0)
+ host[0] = '\0';
+ else
+ host[sizeof (host) - 1] = '\0';
+
+ pw = getpwuid (getuid ());
+ if (pw != NULL)
+ *login = g_strdup_printf ("%s@%s", pw->pw_name, host);
+ else
+ *login = g_strdup (host);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Show current directory in the xterm title */
+void
+update_xterm_title_path (void)
+{
+ if (mc_global.tty.xterm_flag && xterm_title)
+ {
+ char *p;
+ char *path;
+ char *login;
+
+ title_path_prepare (&path, &login);
+
+ p = g_strdup_printf ("mc [%s]:%s", login, path);
+ g_free (login);
+ g_free (path);
+
+ fprintf (stdout, "\33]0;%s\7", str_term_form (p));
+ g_free (p);
+
+ if (!mc_global.tty.alternate_plus_minus)
+ numeric_keypad_mode ();
+ (void) fflush (stdout);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/layout.h b/src/filemanager/layout.h
new file mode 100644
index 0000000..2566cfa
--- /dev/null
+++ b/src/filemanager/layout.h
@@ -0,0 +1,98 @@
+/** \file layout.h
+ * \brief Header: panel layout module
+ */
+
+#ifndef MC__LAYOUT_H
+#define MC__LAYOUT_H
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "panel.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+typedef enum
+{
+ view_listing = 0, /* Directory listing */
+ view_info = 1, /* Information panel */
+ view_tree = 2, /* Tree view */
+ view_quick = 3, /* Quick view */
+ view_nothing = 4, /* Undefined */
+} panel_view_mode_t;
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ gboolean horizontal_split;
+
+ /* vertical split */
+ gboolean vertical_equal;
+ int left_panel_size;
+
+ /* horizontal split */
+ gboolean horizontal_equal;
+ int top_panel_size;
+} panels_layout_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int output_lines;
+extern gboolean command_prompt;
+extern gboolean menubar_visible;
+extern int output_start_y;
+extern gboolean xterm_title;
+extern gboolean free_space;
+extern gboolean nice_rotating_dash;
+
+extern int ok_to_refresh;
+
+extern panels_layout_t panels_layout;
+
+/*** declarations of public functions ************************************************************/
+void layout_change (void);
+void layout_box (void);
+void panel_update_cols (Widget * widget, panel_display_t frame_size);
+void setup_panels (void);
+void panels_split_equal (void);
+void panels_split_more (void);
+void panels_split_less (void);
+void destroy_panels (void);
+void setup_cmdline (void);
+void create_panel (int num, panel_view_mode_t type);
+void swap_panels (void);
+panel_view_mode_t get_panel_type (int idx);
+panel_view_mode_t get_current_type (void);
+panel_view_mode_t get_other_type (void);
+int get_current_index (void);
+int get_other_index (void);
+const char *get_nth_panel_name (int num);
+
+Widget *get_panel_widget (int idx);
+
+WPanel *get_other_panel (void);
+
+void save_panel_dir (int idx);
+char *get_panel_dir_for (const WPanel * widget);
+
+void set_hintbar (const char *str);
+
+/* Rotating dash routines */
+void use_dash (gboolean flag); /* Disable/Enable rotate_dash routines */
+void rotate_dash (gboolean show);
+
+#ifdef ENABLE_SUBSHELL
+gboolean do_load_prompt (void);
+int load_prompt (int fd, void *unused);
+#endif
+
+void update_xterm_title_path (void);
+
+void title_path_prepare (char **path, char **login);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__LAYOUT_H */
diff --git a/src/filemanager/mountlist.c b/src/filemanager/mountlist.c
new file mode 100644
index 0000000..d7fd734
--- /dev/null
+++ b/src/filemanager/mountlist.c
@@ -0,0 +1,1575 @@
+/*
+ Return a list of mounted file systems
+
+ Copyright (C) 1991-2023
+ Free Software Foundation, Inc.
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file mountlist.c
+ * \brief Source: list of mounted filesystems
+ */
+
+#include <config.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h> /* SIZE_MAX */
+#include <sys/types.h>
+
+#include <errno.h>
+
+/* This header needs to be included before sys/mount.h on *BSD */
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+#if defined STAT_STATVFS || defined STAT_STATVFS64 /* POSIX 1003.1-2001 (and later) with XSI */
+#include <sys/statvfs.h>
+#else
+/* Don't include backward-compatibility files unless they're needed.
+ Eventually we'd like to remove all this cruft. */
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#ifdef MOUNTED_GETFSSTAT /* OSF_1, also (obsolete) Apple Darwin 1.3 */
+#ifdef HAVE_SYS_UCRED_H
+#include <grp.h> /* needed on OSF V4.0 for definition of NGROUPS,
+ NGROUPS is used as an array dimension in ucred.h */
+#include <sys/ucred.h> /* needed by powerpc-apple-darwin1.3.7 */
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif
+#ifdef HAVE_SYS_FS_TYPES_H
+#include <sys/fs_types.h> /* needed by powerpc-apple-darwin1.3.7 */
+#endif
+#ifdef HAVE_STRUCT_FSSTAT_F_FSTYPENAME
+#define FS_TYPE(Ent) ((Ent).f_fstypename)
+#else
+#define FS_TYPE(Ent) mnt_names[(Ent).f_type]
+#endif
+#endif /* MOUNTED_GETFSSTAT */
+#endif /* STAT_STATVFS || STAT_STATVFS64 */
+
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif
+#ifdef HAVE_SYS_FS_S5PARAM_H /* Fujitsu UXP/V */
+#include <sys/fs/s5param.h>
+#endif
+#ifdef HAVE_SYS_STATFS_H
+#include <sys/statfs.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
+ also (obsolete) 4.3BSD, SunOS */
+#include <mntent.h>
+#include <sys/types.h>
+#if defined __ANDROID__ /* Android */
+ /* Bionic versions from between 2014-01-09 and 2015-01-08 define MOUNTED to
+ an incorrect value; older Bionic versions don't define it at all. */
+#undef MOUNTED
+#define MOUNTED "/proc/mounts"
+#elif !defined MOUNTED
+#ifdef _PATH_MOUNTED /* GNU libc */
+#define MOUNTED _PATH_MOUNTED
+#endif
+#ifdef MNT_MNTTAB /* HP-UX. */
+#define MOUNTED MNT_MNTTAB
+#endif
+#endif
+#endif
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+#include <sys/mount.h>
+#endif
+
+#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */
+#include <sys/statvfs.h>
+#endif
+
+#ifdef MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */
+#include <fs_info.h>
+#include <dirent.h>
+#endif
+
+#ifdef MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */
+#include <mnttab.h>
+#include <sys/fstyp.h>
+#include <sys/statfs.h>
+#endif
+
+#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */
+#include <sys/mnttab.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */
+#include <sys/mnttab.h>
+#endif
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+#include <fshelp.h>
+#include <sys/vfs.h>
+#endif
+
+#ifdef MOUNTED_INTERIX_STATVFS /* Interix */
+#include <sys/statvfs.h>
+#include <dirent.h>
+#endif
+
+#ifdef HAVE_SYS_MNTENT_H
+/* This is to get MNTOPT_IGNORE on e.g. SVR4. */
+#include <sys/mntent.h>
+#endif
+
+#ifdef MOUNTED_GETMNTENT1
+#if !HAVE_SETMNTENT /* Android <= 4.4 */
+#define setmntent(fp,mode) fopen (fp, mode)
+#endif
+#if !HAVE_ENDMNTENT /* Android <= 4.4 */
+#define endmntent(fp) fclose (fp)
+#endif
+#endif
+
+#ifndef HAVE_HASMNTOPT
+#define hasmntopt(mnt, opt) ((char *) 0)
+#endif
+
+#undef MNT_IGNORE
+#ifdef MNTOPT_IGNORE
+#if defined __sun && defined __SVR4
+/* Solaris defines hasmntopt(struct mnttab *, char *)
+ while it is otherwise hasmntopt(struct mnttab *, const char *). */
+#define MNT_IGNORE(M) hasmntopt (M, (char *) MNTOPT_IGNORE)
+#else
+#define MNT_IGNORE(M) hasmntopt (M, MNTOPT_IGNORE)
+#endif
+#else
+#define MNT_IGNORE(M) 0
+#endif
+
+#ifdef HAVE_INFOMOUNT_QNX
+#include <sys/disk.h>
+#include <sys/fsys.h>
+#endif
+
+#ifdef HAVE_SYS_STATVFS_H /* SVR4. */
+#include <sys/statvfs.h>
+#endif
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_verscmp() */
+#include "lib/unixcompat.h" /* makedev */
+#include "mountlist.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#if defined (__QNX__) && !defined(__QNXNTO__) && !defined (HAVE_INFOMOUNT_LIST)
+#define HAVE_INFOMOUNT_QNX
+#endif
+
+#if defined(HAVE_INFOMOUNT_LIST) || defined(HAVE_INFOMOUNT_QNX)
+#define HAVE_INFOMOUNT
+#endif
+
+/* The results of opendir() in this file are not used with dirfd and fchdir,
+ therefore save some unnecessary work in fchdir.c. */
+#undef opendir
+#undef closedir
+
+#define ME_DUMMY_0(Fs_name, Fs_type) \
+ (strcmp (Fs_type, "autofs") == 0 \
+ || strcmp (Fs_type, "proc") == 0 \
+ || strcmp (Fs_type, "subfs") == 0 \
+ /* for Linux 2.6/3.x */ \
+ || strcmp (Fs_type, "debugfs") == 0 \
+ || strcmp (Fs_type, "devpts") == 0 \
+ || strcmp (Fs_type, "fusectl") == 0 \
+ || strcmp (Fs_type, "fuse.portal") == 0 \
+ || strcmp (Fs_type, "mqueue") == 0 \
+ || strcmp (Fs_type, "rpc_pipefs") == 0 \
+ || strcmp (Fs_type, "sysfs") == 0 \
+ /* FreeBSD, Linux 2.4 */ \
+ || strcmp (Fs_type, "devfs") == 0 \
+ /* for NetBSD 3.0 */ \
+ || strcmp (Fs_type, "kernfs") == 0 \
+ /* for Irix 6.5 */ \
+ || strcmp (Fs_type, "ignore") == 0)
+
+/* Historically, we have marked as "dummy" any file system of type "none",
+ but now that programs like du need to know about bind-mounted directories,
+ we grant an exception to any with "bind" in its list of mount options.
+ I.e., those are *not* dummy entries. */
+#ifdef MOUNTED_GETMNTENT1
+#define ME_DUMMY(Fs_name, Fs_type, Bind) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) \
+ || (strcmp (Fs_type, "none") == 0 && !Bind))
+#else
+#define ME_DUMMY(Fs_name, Fs_type) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
+#endif
+
+#ifdef __CYGWIN__
+#include <windows.h>
+#define ME_REMOTE me_remote
+/* All cygwin mount points include ':' or start with '//'; so it
+ requires a native Windows call to determine remote disks. */
+static int
+me_remote (char const *fs_name, char const *fs_type)
+{
+ (void) fs_type;
+
+ if (fs_name[0] && fs_name[1] == ':')
+ {
+ char drive[4];
+ sprintf (drive, "%c:\\", fs_name[0]);
+ switch (GetDriveType (drive))
+ {
+ case DRIVE_REMOVABLE:
+ case DRIVE_FIXED:
+ case DRIVE_CDROM:
+ case DRIVE_RAMDISK:
+ return 0;
+ }
+ }
+ return 1;
+}
+#endif
+#ifndef ME_REMOTE
+/* A file system is 'remote' if its Fs_name contains a ':'
+ or if (it is of type (smbfs or smb3 or cifs) and its Fs_name starts with '//')
+ or if it is of any other of the listed types
+ or Fs_name is equal to "-hosts" (used by autofs to mount remote fs).
+ "VM" file systems like prl_fs or vboxsf are not considered remote here. */
+#define ME_REMOTE(Fs_name, Fs_type) \
+ (strchr (Fs_name, ':') != NULL \
+ || ((Fs_name)[0] == '/' \
+ && (Fs_name)[1] == '/' \
+ && (strcmp (Fs_type, "smbfs") == 0 \
+ || strcmp (Fs_type, "smb3") == 0 \
+ || strcmp (Fs_type, "cifs") == 0)) \
+ || strcmp (Fs_type, "acfs") == 0 \
+ || strcmp (Fs_type, "afs") == 0 \
+ || strcmp (Fs_type, "coda") == 0 \
+ || strcmp (Fs_type, "auristorfs") == 0 \
+ || strcmp (Fs_type, "fhgfs") == 0 \
+ || strcmp (Fs_type, "gpfs") == 0 \
+ || strcmp (Fs_type, "ibrix") == 0 \
+ || strcmp (Fs_type, "ocfs2") == 0 \
+ || strcmp (Fs_type, "vxfs") == 0 \
+ || strcmp ("-hosts", Fs_name) == 0)
+#endif
+
+/* Many space usage primitives use all 1 bits to denote a value that is
+ not applicable or unknown. Propagate this information by returning
+ a uintmax_t value that is all 1 bits if X is all 1 bits, even if X
+ is unsigned and narrower than uintmax_t. */
+#define PROPAGATE_ALL_ONES(x) \
+ ((sizeof (x) < sizeof (uintmax_t) \
+ && (~ (x) == (sizeof (x) < sizeof (int) \
+ ? - (1 << (sizeof (x) * CHAR_BIT)) \
+ : 0))) \
+ ? UINTMAX_MAX : (uintmax_t) (x))
+
+/* Extract the top bit of X as an uintmax_t value. */
+#define EXTRACT_TOP_BIT(x) ((x) & ((uintmax_t) 1 << (sizeof (x) * CHAR_BIT - 1)))
+
+/* If a value is negative, many space usage primitives store it into an
+ integer variable by assignment, even if the variable's type is unsigned.
+ So, if a space usage variable X's top bit is set, convert X to the
+ uintmax_t value V such that (- (uintmax_t) V) is the negative of
+ the original value. If X's top bit is clear, just yield X.
+ Use PROPAGATE_TOP_BIT if the original value might be negative;
+ otherwise, use PROPAGATE_ALL_ONES. */
+#define PROPAGATE_TOP_BIT(x) ((x) | ~ (EXTRACT_TOP_BIT (x) - 1))
+
+#ifdef STAT_STATVFS
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+/* The FRSIZE fallback is not required in this case. */
+#undef STAT_STATFS2_FRSIZE
+#else
+#include <sys/utsname.h>
+#include <sys/statfs.h>
+#define STAT_STATFS2_BSIZE 1
+#endif
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+/* A mount table entry. */
+struct mount_entry
+{
+ char *me_devname; /* Device node name, including "/dev/". */
+ char *me_mountdir; /* Mount point directory name. */
+ char *me_mntroot; /* Directory on filesystem of device used
+ as root for the (bind) mount. */
+ char *me_type; /* "nfs", "4.2", etc. */
+ dev_t me_dev; /* Device number of me_mountdir. */
+ unsigned int me_dummy:1; /* Nonzero for dummy file systems. */
+ unsigned int me_remote:1; /* Nonzero for remote filesystems. */
+ unsigned int me_type_malloced:1; /* Nonzero if me_type was malloced. */
+};
+
+struct fs_usage
+{
+ uintmax_t fsu_blocksize; /* Size of a block. */
+ uintmax_t fsu_blocks; /* Total blocks. */
+ uintmax_t fsu_bfree; /* Free blocks available to superuser. */
+ uintmax_t fsu_bavail; /* Free blocks available to non-superuser. */
+ int fsu_bavail_top_bit_set; /* 1 if fsu_bavail represents a value < 0. */
+ uintmax_t fsu_files; /* Total file nodes. */
+ uintmax_t fsu_ffree; /* Free file nodes. */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+#ifdef HAVE_INFOMOUNT_LIST
+static GSList *mc_mount_list = NULL;
+#endif /* HAVE_INFOMOUNT_LIST */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef STAT_STATVFS
+/* Return true if statvfs works. This is false for statvfs on systems
+ with GNU libc on Linux kernels before 2.6.36, which stats all
+ preceding entries in /proc/mounts; that makes df hang if even one
+ of the corresponding file systems is hard-mounted but not available. */
+static int
+statvfs_works (void)
+{
+#if ! (defined __linux__ && (defined __GLIBC__ || defined __UCLIBC__))
+ return 1;
+#else
+ static int statvfs_works_cache = -1;
+ struct utsname name;
+
+ if (statvfs_works_cache < 0)
+ statvfs_works_cache = (uname (&name) == 0 && 0 <= str_verscmp (name.release, "2.6.36"));
+ return statvfs_works_cache;
+#endif
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT_LIST
+static void
+free_mount_entry (struct mount_entry *me)
+{
+ if (me == NULL)
+ return;
+ g_free (me->me_devname);
+ g_free (me->me_mountdir);
+ g_free (me->me_mntroot);
+ if (me->me_type_malloced)
+ g_free (me->me_type);
+ g_free (me);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+
+#ifndef HAVE_STRUCT_STATFS_F_FSTYPENAME
+static char *
+fstype_to_string (short int t)
+{
+ switch (t)
+ {
+#ifdef MOUNT_PC
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PC:
+ return "pc";
+#endif
+#ifdef MOUNT_MFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_MFS:
+ return "mfs";
+#endif
+#ifdef MOUNT_LO
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LO:
+ return "lo";
+#endif
+#ifdef MOUNT_TFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_TFS:
+ return "tfs";
+#endif
+#ifdef MOUNT_TMP
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_TMP:
+ return "tmp";
+#endif
+#ifdef MOUNT_UFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UFS:
+ return "ufs";
+#endif
+#ifdef MOUNT_NFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_NFS:
+ return "nfs";
+#endif
+#ifdef MOUNT_MSDOS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_MSDOS:
+ return "msdos";
+#endif
+#ifdef MOUNT_LFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LFS:
+ return "lfs";
+#endif
+#ifdef MOUNT_LOFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_LOFS:
+ return "lofs";
+#endif
+#ifdef MOUNT_FDESC
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_FDESC:
+ return "fdesc";
+#endif
+#ifdef MOUNT_PORTAL
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PORTAL:
+ return "portal";
+#endif
+#ifdef MOUNT_NULL
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_NULL:
+ return "null";
+#endif
+#ifdef MOUNT_UMAP
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UMAP:
+ return "umap";
+#endif
+#ifdef MOUNT_KERNFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_KERNFS:
+ return "kernfs";
+#endif
+#ifdef MOUNT_PROCFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_PROCFS:
+ return "procfs";
+#endif
+#ifdef MOUNT_AFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_AFS:
+ return "afs";
+#endif
+#ifdef MOUNT_CD9660
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_CD9660:
+ return "cd9660";
+#endif
+#ifdef MOUNT_UNION
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_UNION:
+ return "union";
+#endif
+#ifdef MOUNT_DEVFS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_DEVFS:
+ return "devfs";
+#endif
+#ifdef MOUNT_EXT2FS
+ /* cppcheck-suppress syntaxError */
+ case MOUNT_EXT2FS:
+ return "ext2fs";
+#endif
+ default:
+ return "?";
+ }
+}
+#endif /* ! HAVE_STRUCT_STATFS_F_FSTYPENAME */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+fsp_to_string (const struct statfs *fsp)
+{
+#ifdef HAVE_STRUCT_STATFS_F_FSTYPENAME
+ return (char *) (fsp->f_fstypename);
+#else
+ return fstype_to_string (fsp->f_type);
+#endif
+}
+#endif /* MOUNTED_GETMNTINFO */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+static char *
+fstype_to_string (int t)
+{
+ struct vfs_ent *e;
+
+ e = getvfsbytype (t);
+ if (!e || !e->vfsent_name)
+ return "none";
+ else
+ return e->vfsent_name;
+}
+#endif /* MOUNTED_VMOUNT */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined MOUNTED_GETMNTENT1 || defined MOUNTED_GETMNTENT2
+
+/* Return the device number from MOUNT_OPTIONS, if possible.
+ Otherwise return (dev_t) -1. */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static dev_t
+dev_from_mount_options (char const *mount_options)
+{
+ /* GNU/Linux allows file system implementations to define their own
+ meaning for "dev=" mount options, so don't trust the meaning
+ here. */
+#ifndef __linux__
+ static char const dev_pattern[] = ",dev=";
+ char const *devopt = strstr (mount_options, dev_pattern);
+
+ if (devopt)
+ {
+ char const *optval = devopt + sizeof (dev_pattern) - 1;
+ char *optvalend;
+ unsigned long int dev;
+ errno = 0;
+ dev = strtoul (optval, &optvalend, 16);
+ if (optval != optvalend
+ && (*optvalend == '\0' || *optvalend == ',')
+ && !(dev == ULONG_MAX && errno == ERANGE) && dev == (dev_t) dev)
+ return dev;
+ }
+#endif
+
+ (void) mount_options;
+ return -1;
+}
+
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if defined MOUNTED_GETMNTENT1 && (defined __linux__ || defined __ANDROID__) /* GNU/Linux, Android */
+
+/* Unescape the paths in mount tables.
+ STR is updated in place. */
+static void
+unescape_tab (char *str)
+{
+ size_t i, j = 0;
+ size_t len;
+
+ len = strlen (str) + 1;
+
+ for (i = 0; i < len; i++)
+ {
+ if (str[i] == '\\' && (i + 4 < len)
+ && str[i + 1] >= '0' && str[i + 1] <= '3'
+ && str[i + 2] >= '0' && str[i + 2] <= '7' && str[i + 3] >= '0' && str[i + 3] <= '7')
+ {
+ str[j++] = (str[i + 1] - '0') * 64 + (str[i + 2] - '0') * 8 + (str[i + 3] - '0');
+ i += 3;
+ }
+ else
+ str[j++] = str[i];
+ }
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return a list of the currently mounted file systems, or NULL on error.
+ Add each entry to the tail of the list so that they stay in order. */
+
+static GSList *
+read_file_system_list (void)
+{
+ GSList *mount_list = NULL;
+ struct mount_entry *me;
+
+#ifdef MOUNTED_GETMNTENT1 /* glibc, HP-UX, IRIX, Cygwin, Android,
+ also (obsolete) 4.3BSD, SunOS */
+ {
+ FILE *fp;
+
+#if defined __linux__ || defined __ANDROID__
+ /* Try parsing mountinfo first, as that make device IDs available.
+ Note we could use libmount routines to simplify this parsing a little
+ (and that code is in previous versions of this function), however
+ libmount depends on libselinux which pulls in many dependencies. */
+ char const *mountinfo = "/proc/self/mountinfo";
+
+ fp = fopen (mountinfo, "r");
+ if (fp != NULL)
+ {
+ char *line = NULL;
+ size_t buf_size = 0;
+
+ while (getline (&line, &buf_size, fp) != -1)
+ {
+ unsigned int devmaj, devmin;
+ int target_s, target_e, type_s, type_e;
+ int source_s, source_e, mntroot_s, mntroot_e;
+ char test;
+ char *dash;
+ int rc;
+
+ rc = sscanf (line, "%*u " /* id - discarded */
+ "%*u " /* parent - discarded */
+ "%u:%u " /* dev major:minor */
+ "%n%*s%n " /* mountroot */
+ "%n%*s%n" /* target, start and end */
+ "%c", /* more data... */
+ &devmaj, &devmin, &mntroot_s, &mntroot_e, &target_s, &target_e, &test);
+
+ if (rc != 3 && rc != 7) /* 7 if %n included in count. */
+ continue;
+
+ /* skip optional fields, terminated by " - " */
+ dash = strstr (line + target_e, " - ");
+ if (dash == NULL)
+ continue;
+
+ rc = sscanf (dash, " - " /* */
+ "%n%*s%n " /* FS type, start and end */
+ "%n%*s%n " /* source, start and end */
+ "%c", /* more data... */
+ &type_s, &type_e, &source_s, &source_e, &test);
+ if (rc != 1 && rc != 5) /* 5 if %n included in count. */
+ continue;
+
+ /* manipulate the sub-strings in place. */
+ line[mntroot_e] = '\0';
+ line[target_e] = '\0';
+ dash[type_e] = '\0';
+ dash[source_e] = '\0';
+ unescape_tab (dash + source_s);
+ unescape_tab (line + target_s);
+ unescape_tab (line + mntroot_s);
+
+ me = g_malloc (sizeof *me);
+
+ me->me_devname = g_strdup (dash + source_s);
+ me->me_mountdir = g_strdup (line + target_s);
+ me->me_mntroot = g_strdup (line + mntroot_s);
+ me->me_type = g_strdup (dash + type_s);
+ me->me_type_malloced = 1;
+ me->me_dev = makedev (devmaj, devmin);
+ /* we pass "false" for the "Bind" option as that's only
+ significant when the Fs_type is "none" which will not be
+ the case when parsing "/proc/self/mountinfo", and only
+ applies for static /etc/mtab files. */
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, FALSE);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ free (line);
+
+ if (ferror (fp) != 0)
+ {
+ int saved_errno = errno;
+
+ fclose (fp);
+ errno = saved_errno;
+ goto free_then_fail;
+ }
+
+ if (fclose (fp) == EOF)
+ goto free_then_fail;
+ }
+ else /* fallback to /proc/self/mounts (/etc/mtab). */
+#endif /* __linux __ || __ANDROID__ */
+ {
+ struct mntent *mnt;
+ const char *table = MOUNTED;
+
+ fp = setmntent (table, "r");
+ if (fp == NULL)
+ return NULL;
+
+ while ((mnt = getmntent (fp)) != NULL)
+ {
+ gboolean bind;
+
+ bind = hasmntopt (mnt, "bind") != NULL;
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt->mnt_fsname);
+ me->me_mountdir = g_strdup (mnt->mnt_dir);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt->mnt_type);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = dev_from_mount_options (mnt->mnt_opts);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ if (endmntent (fp) == 0)
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETMNTENT1. */
+
+#ifdef MOUNTED_GETMNTINFO /* Mac OS X, FreeBSD, OpenBSD, also (obsolete) 4.4BSD */
+ {
+ struct statfs *fsp;
+ int entries;
+
+ entries = getmntinfo (&fsp, MNT_NOWAIT);
+ if (entries < 0)
+ return NULL;
+ for (; entries-- > 0; fsp++)
+ {
+ char *fs_type = fsp_to_string (fsp);
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (fsp->f_mntfromname);
+ me->me_mountdir = g_strdup (fsp->f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = fs_type;
+ me->me_type_malloced = 0;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+#endif /* MOUNTED_GETMNTINFO */
+
+#ifdef MOUNTED_GETMNTINFO2 /* NetBSD, Minix */
+ {
+ struct statvfs *fsp;
+ int entries;
+
+ entries = getmntinfo (&fsp, MNT_NOWAIT);
+ if (entries < 0)
+ return NULL;
+ for (; entries-- > 0; fsp++)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (fsp->f_mntfromname);
+ me->me_mountdir = g_strdup (fsp->f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fsp->f_fstypename);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+#endif /* MOUNTED_GETMNTINFO2 */
+
+#if defined MOUNTED_FS_STAT_DEV /* Haiku, also (obsolete) BeOS */
+ {
+ /* The next_dev() and fs_stat_dev() system calls give the list of
+ all file systems, including the information returned by statvfs()
+ (fs type, total blocks, free blocks etc.), but without the mount
+ point. But on BeOS all file systems except / are mounted in the
+ rootfs, directly under /.
+ The directory name of the mount point is often, but not always,
+ identical to the volume name of the device.
+ We therefore get the list of subdirectories of /, and the list
+ of all file systems, and match the two lists. */
+
+ DIR *dirp;
+ struct rootdir_entry
+ {
+ char *name;
+ dev_t dev;
+ ino_t ino;
+ struct rootdir_entry *next;
+ };
+ struct rootdir_entry *rootdir_list;
+ struct rootdir_entry **rootdir_tail;
+ int32 pos;
+ dev_t dev;
+ fs_info fi;
+
+ /* All volumes are mounted in the rootfs, directly under /. */
+ rootdir_list = NULL;
+ rootdir_tail = &rootdir_list;
+ dirp = opendir (PATH_SEP_STR);
+ if (dirp)
+ {
+ struct dirent *d;
+
+ while ((d = readdir (dirp)) != NULL)
+ {
+ char *name;
+ struct stat statbuf;
+
+ if (DIR_IS_DOT (d->d_name))
+ continue;
+
+ if (DIR_IS_DOTDOT (d->d_name))
+ name = g_strdup (PATH_SEP_STR);
+ else
+ name = g_strconcat (PATH_SEP_STR, d->d_name, (char *) NULL);
+
+ if (lstat (name, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
+ {
+ struct rootdir_entry *re = g_malloc (sizeof (*re));
+ re->name = name;
+ re->dev = statbuf.st_dev;
+ re->ino = statbuf.st_ino;
+
+ /* Add to the linked list. */
+ *rootdir_tail = re;
+ rootdir_tail = &re->next;
+ }
+ else
+ g_free (name);
+ }
+ closedir (dirp);
+ }
+ *rootdir_tail = NULL;
+
+ for (pos = 0; (dev = next_dev (&pos)) >= 0;)
+ if (fs_stat_dev (dev, &fi) >= 0)
+ {
+ /* Note: fi.dev == dev. */
+ struct rootdir_entry *re;
+
+ for (re = rootdir_list; re; re = re->next)
+ if (re->dev == fi.dev && re->ino == fi.root)
+ break;
+
+ me = g_malloc (sizeof (*me));
+ me->me_devname =
+ g_strdup (fi.device_name[0] != '\0' ? fi.device_name : fi.fsh_name);
+ me->me_mountdir = g_strdup (re != NULL ? re->name : fi.fsh_name);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fi.fsh_name);
+ me->me_type_malloced = 1;
+ me->me_dev = fi.dev;
+ me->me_dummy = 0;
+ me->me_remote = (fi.flags & B_FS_IS_SHARED) != 0;
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ while (rootdir_list != NULL)
+ {
+ struct rootdir_entry *re = rootdir_list;
+
+ rootdir_list = re->next;
+ g_free (re->name);
+ g_free (re);
+ }
+ }
+#endif /* MOUNTED_FS_STAT_DEV */
+
+#ifdef MOUNTED_GETFSSTAT /* OSF/1, also (obsolete) Apple Darwin 1.3 */
+ {
+ int numsys, counter;
+ size_t bufsize;
+ struct statfs *stats;
+
+ numsys = getfsstat (NULL, 0L, MNT_NOWAIT);
+ if (numsys < 0)
+ return NULL;
+ if (SIZE_MAX / sizeof (*stats) <= numsys)
+ {
+ fprintf (stderr, "%s\n", _("Memory exhausted!"));
+ exit (EXIT_FAILURE);
+ }
+
+ bufsize = (1 + numsys) * sizeof (*stats);
+ stats = g_malloc (bufsize);
+ numsys = getfsstat (stats, bufsize, MNT_NOWAIT);
+
+ if (numsys < 0)
+ {
+ g_free (stats);
+ return NULL;
+ }
+
+ for (counter = 0; counter < numsys; counter++)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (stats[counter].f_mntfromname);
+ me->me_mountdir = g_strdup (stats[counter].f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (FS_TYPE (stats[counter]));
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ g_free (stats);
+ }
+#endif /* MOUNTED_GETFSSTAT */
+
+#if defined MOUNTED_FREAD_FSTYP /* (obsolete) SVR3 */
+ {
+ struct mnttab mnt;
+ char *table = "/etc/mnttab";
+ FILE *fp;
+
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ return NULL;
+
+ while (fread (&mnt, sizeof (mnt), 1, fp) > 0)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt.mt_dev);
+ me->me_mountdir = g_strdup (mnt.mt_filsys);
+ me->me_mntroot = NULL;
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+ me->me_type = "";
+ me->me_type_malloced = 0;
+ {
+ struct statfs fsd;
+ char typebuf[FSTYPSZ];
+
+ if (statfs (me->me_mountdir, &fsd, sizeof (fsd), 0) != -1
+ && sysfs (GETFSTYP, fsd.f_fstyp, typebuf) != -1)
+ {
+ me->me_type = g_strdup (typebuf);
+ me->me_type_malloced = 1;
+ }
+ }
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ if (ferror (fp))
+ {
+ /* The last fread() call must have failed. */
+ int saved_errno = errno;
+
+ fclose (fp);
+ errno = saved_errno;
+ goto free_then_fail;
+ }
+
+ if (fclose (fp) == EOF)
+ goto free_then_fail;
+ }
+#endif /* MOUNTED_FREAD_FSTYP */
+
+#ifdef MOUNTED_GETEXTMNTENT /* Solaris >= 8 */
+ {
+ struct extmnttab mnt;
+ const char *table = MNTTAB;
+ FILE *fp;
+ int ret;
+
+ /* No locking is needed, because the contents of /etc/mnttab is generated by the kernel. */
+
+ errno = 0;
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ ret = errno;
+ else
+ {
+ while ((ret = getextmntent (fp, &mnt, 1)) == 0)
+ {
+ me = g_malloc (sizeof *me);
+ me->me_devname = g_strdup (mnt.mnt_special);
+ me->me_mountdir = g_strdup (mnt.mnt_mountp);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt.mnt_fstype);
+ me->me_type_malloced = 1;
+ me->me_dummy = MNT_IGNORE (&mnt) != 0;
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = makedev (mnt.mnt_major, mnt.mnt_minor);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
+ /* Here ret = -1 means success, ret >= 0 means failure. */
+ }
+
+ if (ret >= 0)
+ {
+ errno = ret;
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETEXTMNTENT */
+
+#ifdef MOUNTED_GETMNTENT2 /* Solaris < 8, also (obsolete) SVR4 */
+ {
+ struct mnttab mnt;
+ const char *table = MNTTAB;
+ FILE *fp;
+ int ret;
+ int lockfd = -1;
+
+#if defined F_RDLCK && defined F_SETLKW
+ /* MNTTAB_LOCK is a macro name of our own invention; it's not present in
+ e.g. Solaris 2.6. If the SVR4 folks ever define a macro
+ for this file name, we should use their macro name instead.
+ (Why not just lock MNTTAB directly? We don't know.) */
+#ifndef MNTTAB_LOCK
+#define MNTTAB_LOCK "/etc/.mnttab.lock"
+#endif
+ lockfd = open (MNTTAB_LOCK, O_RDONLY);
+ if (lockfd >= 0)
+ {
+ struct flock flock;
+
+ flock.l_type = F_RDLCK;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = 0;
+ flock.l_len = 0;
+ while (fcntl (lockfd, F_SETLKW, &flock) == -1)
+ if (errno != EINTR)
+ {
+ int saved_errno = errno;
+ close (lockfd);
+ errno = saved_errno;
+ return NULL;
+ }
+ }
+ else if (errno != ENOENT)
+ return NULL;
+#endif
+
+ errno = 0;
+ fp = fopen (table, "r");
+ if (fp == NULL)
+ ret = errno;
+ else
+ {
+ while ((ret = getmntent (fp, &mnt)) == 0)
+ {
+ me = g_malloc (sizeof (*me));
+ me->me_devname = g_strdup (mnt.mnt_special);
+ me->me_mountdir = g_strdup (mnt.mnt_mountp);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (mnt.mnt_fstype);
+ me->me_type_malloced = 1;
+ me->me_dummy = MNT_IGNORE (&mnt) != 0;
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = dev_from_mount_options (mnt.mnt_mntopts);
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+
+ ret = fclose (fp) == EOF ? errno : 0 < ret ? 0 : -1;
+ /* Here ret = -1 means success, ret >= 0 means failure. */
+ }
+
+ if (lockfd >= 0 && close (lockfd) != 0)
+ ret = errno;
+
+ if (ret >= 0)
+ {
+ errno = ret;
+ goto free_then_fail;
+ }
+ }
+#endif /* MOUNTED_GETMNTENT2. */
+
+#ifdef MOUNTED_VMOUNT /* AIX */
+ {
+ int bufsize;
+ void *entries;
+ char *thisent;
+ struct vmount *vmp;
+ int n_entries;
+ int i;
+
+ /* Ask how many bytes to allocate for the mounted file system info. */
+ entries = &bufsize;
+ if (mntctl (MCTL_QUERY, sizeof (bufsize), entries) != 0)
+ return NULL;
+ entries = g_malloc (bufsize);
+
+ /* Get the list of mounted file systems. */
+ n_entries = mntctl (MCTL_QUERY, bufsize, entries);
+ if (n_entries < 0)
+ {
+ int saved_errno = errno;
+
+ g_free (entries);
+ errno = saved_errno;
+ return NULL;
+ }
+
+ for (i = 0, thisent = entries; i < n_entries; i++, thisent += vmp->vmt_length)
+ {
+ char *options, *ignore;
+
+ vmp = (struct vmount *) thisent;
+ me = g_malloc (sizeof (*me));
+ if (vmp->vmt_flags & MNT_REMOTE)
+ {
+ char *host, *dir;
+
+ me->me_remote = 1;
+ /* Prepend the remote dirname. */
+ host = thisent + vmp->vmt_data[VMT_HOSTNAME].vmt_off;
+ dir = thisent + vmp->vmt_data[VMT_OBJECT].vmt_off;
+ me->me_devname = g_strconcat (host, ":", dir, (char *) NULL);
+ }
+ else
+ {
+ me->me_remote = 0;
+ me->me_devname = g_strdup (thisent + vmp->vmt_data[VMT_OBJECT].vmt_off);
+ }
+ me->me_mountdir = g_strdup (thisent + vmp->vmt_data[VMT_STUB].vmt_off);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (fstype_to_string (vmp->vmt_gfstype));
+ me->me_type_malloced = 1;
+ options = thisent + vmp->vmt_data[VMT_ARGS].vmt_off;
+ ignore = strstr (options, "ignore");
+ me->me_dummy = (ignore
+ && (ignore == options || ignore[-1] == ',')
+ && (ignore[sizeof ("ignore") - 1] == ','
+ || ignore[sizeof ("ignore") - 1] == '\0'));
+ me->me_dev = (dev_t) (-1); /* vmt_fsid might be the info we want. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ g_free (entries);
+ }
+#endif /* MOUNTED_VMOUNT. */
+
+#ifdef MOUNTED_INTERIX_STATVFS /* Interix */
+ {
+ DIR *dirp = opendir ("/dev/fs");
+ char node[9 + NAME_MAX];
+
+ if (!dirp)
+ goto free_then_fail;
+
+ while (1)
+ {
+ struct statvfs dev;
+ struct dirent entry;
+ struct dirent *result;
+
+ if (readdir_r (dirp, &entry, &result) || result == NULL)
+ break;
+
+ strcpy (node, "/dev/fs/");
+ strcat (node, entry.d_name);
+
+ if (statvfs (node, &dev) == 0)
+ {
+ me = g_malloc (sizeof *me);
+ me->me_devname = g_strdup (dev.f_mntfromname);
+ me->me_mountdir = g_strdup (dev.f_mntonname);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (dev.f_fstypename);
+ me->me_type_malloced = 1;
+ me->me_dummy = ME_DUMMY (me->me_devname, me->me_type);
+ me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
+ me->me_dev = (dev_t) (-1); /* Magic; means not known yet. */
+
+ mount_list = g_slist_prepend (mount_list, me);
+ }
+ }
+ closedir (dirp);
+ }
+#endif /* MOUNTED_INTERIX_STATVFS */
+
+ return g_slist_reverse (mount_list);
+
+ free_then_fail:
+ {
+ int saved_errno = errno;
+
+ g_slist_free_full (mount_list, (GDestroyNotify) free_mount_entry);
+
+ errno = saved_errno;
+ return NULL;
+ }
+}
+#endif /* HAVE_INFOMOUNT_LIST */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT_QNX
+/**
+ ** QNX has no [gs]etmnt*(), [gs]etfs*(), or /etc/mnttab, but can do
+ ** this via the following code.
+ ** Note that, as this is based on CWD, it only fills one mount_entry
+ ** structure. See my_statfs() below for the "other side" of this hack.
+ */
+
+static GSList *
+read_file_system_list (void)
+{
+ struct _disk_entry de;
+ struct statfs fs;
+ int i, fd;
+ char *tp, dev[_POSIX_NAME_MAX], dir[_POSIX_PATH_MAX];
+ struct mount_entry *me = NULL;
+ static GSList *list = NULL;
+
+ if (list != NULL)
+ {
+ me = (struct mount_entry *) list->data;
+
+ g_free (me->me_devname);
+ g_free (me->me_mountdir);
+ g_free (me->me_mntroot);
+ g_free (me->me_type);
+ }
+ else
+ {
+ me = (struct mount_entry *) g_malloc (sizeof (struct mount_entry));
+ list = g_slist_prepend (list, me);
+ }
+
+ if (!getcwd (dir, _POSIX_PATH_MAX))
+ return (NULL);
+
+ fd = open (dir, O_RDONLY);
+ if (fd == -1)
+ return (NULL);
+
+ i = disk_get_entry (fd, &de);
+
+ close (fd);
+
+ if (i == -1)
+ return (NULL);
+
+ switch (de.disk_type)
+ {
+ case _UNMOUNTED:
+ tp = "unmounted";
+ break;
+ case _FLOPPY:
+ tp = "Floppy";
+ break;
+ case _HARD:
+ tp = "Hard";
+ break;
+ case _RAMDISK:
+ tp = "Ram";
+ break;
+ case _REMOVABLE:
+ tp = "Removable";
+ break;
+ case _TAPE:
+ tp = "Tape";
+ break;
+ case _CDROM:
+ tp = "CDROM";
+ break;
+ default:
+ tp = "unknown";
+ }
+
+ if (fsys_get_mount_dev (dir, &dev) == -1)
+ return (NULL);
+
+ if (fsys_get_mount_pt (dev, &dir) == -1)
+ return (NULL);
+
+ me->me_devname = g_strdup (dev);
+ me->me_mountdir = g_strdup (dir);
+ me->me_mntroot = NULL;
+ me->me_type = g_strdup (tp);
+ me->me_dev = de.disk_type;
+
+#ifdef DEBUG
+ fprintf (stderr,
+ "disk_get_entry():\n\tdisk_type=%d (%s)\n\tdriver_name='%-*.*s'\n\tdisk_drv=%d\n",
+ de.disk_type, tp, _DRIVER_NAME_LEN, _DRIVER_NAME_LEN, de.driver_name, de.disk_drv);
+ fprintf (stderr, "fsys_get_mount_dev():\n\tdevice='%s'\n", dev);
+ fprintf (stderr, "fsys_get_mount_pt():\n\tmount point='%s'\n", dir);
+#endif /* DEBUG */
+
+ return (list);
+}
+#endif /* HAVE_INFOMOUNT_QNX */
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_INFOMOUNT
+/* Fill in the fields of FSP with information about space usage for
+ the file system on which FILE resides.
+ DISK is the device on which FILE is mounted, for space-getting
+ methods that need to know it.
+ Return 0 if successful, -1 if not. When returning -1, ensure that
+ ERRNO is either a system error value, or zero if DISK is NULL
+ on a system that requires a non-NULL value. */
+static int
+get_fs_usage (char const *file, char const *disk, struct fs_usage *fsp)
+{
+#ifdef STAT_STATVFS /* POSIX, except pre-2.6.36 glibc/Linux */
+
+ if (statvfs_works ())
+ {
+ struct statvfs vfsd;
+
+ if (statvfs (file, &vfsd) < 0)
+ return -1;
+
+ /* f_frsize isn't guaranteed to be supported. */
+ fsp->fsu_blocksize = (vfsd.f_frsize
+ ? PROPAGATE_ALL_ONES (vfsd.f_frsize)
+ : PROPAGATE_ALL_ONES (vfsd.f_bsize));
+
+ fsp->fsu_blocks = PROPAGATE_ALL_ONES (vfsd.f_blocks);
+ fsp->fsu_bfree = PROPAGATE_ALL_ONES (vfsd.f_bfree);
+ fsp->fsu_bavail = PROPAGATE_TOP_BIT (vfsd.f_bavail);
+ fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (vfsd.f_bavail) != 0;
+ fsp->fsu_files = PROPAGATE_ALL_ONES (vfsd.f_files);
+ fsp->fsu_ffree = PROPAGATE_ALL_ONES (vfsd.f_ffree);
+ }
+ else
+#endif
+
+ {
+#if defined STAT_STATVFS64 /* AIX */
+
+ struct statvfs64 fsd;
+
+ if (statvfs64 (file, &fsd) < 0)
+ return -1;
+
+ /* f_frsize isn't guaranteed to be supported. */
+ /* *INDENT-OFF* */
+ fsp->fsu_blocksize = fsd.f_frsize
+ ? PROPAGATE_ALL_ONES (fsd.f_frsize)
+ : PROPAGATE_ALL_ONES (fsd.f_bsize);
+ /* *INDENT-ON* */
+
+#elif defined STAT_STATFS3_OSF1 /* OSF/1 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd, sizeof (struct statfs)) != 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
+
+#elif defined STAT_STATFS2_FRSIZE /* 2.6 < glibc/Linux < 2.6.36 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_frsize);
+
+#elif defined STAT_STATFS2_BSIZE /* glibc/Linux < 2.6, 4.3BSD, SunOS 4, \
+ Mac OS X < 10.4, FreeBSD < 5.0, \
+ NetBSD < 3.0, OpenBSD < 4.4 */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_bsize);
+
+#ifdef STATFS_TRUNCATES_BLOCK_COUNTS
+
+ /* In SunOS 4.1.2, 4.1.3, and 4.1.3_U1, the block counts in the
+ struct statfs are truncated to 2GB. These conditions detect that
+ truncation, presumably without botching the 4.1.1 case, in which
+ the values are not truncated. The correct counts are stored in
+ undocumented spare fields. */
+ if (fsd.f_blocks == 0x7fffffff / fsd.f_bsize && fsd.f_spare[0] > 0)
+ {
+ fsd.f_blocks = fsd.f_spare[0];
+ fsd.f_bfree = fsd.f_spare[1];
+ fsd.f_bavail = fsd.f_spare[2];
+ }
+#endif /* STATFS_TRUNCATES_BLOCK_COUNTS */
+
+#elif defined STAT_STATFS2_FSIZE /* 4.4BSD and older NetBSD */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd) < 0)
+ return -1;
+
+ fsp->fsu_blocksize = PROPAGATE_ALL_ONES (fsd.f_fsize);
+
+#elif defined STAT_STATFS4 /* SVR3, old Irix */
+
+ struct statfs fsd;
+
+ if (statfs (file, &fsd, sizeof (fsd), 0) < 0)
+ return -1;
+
+ /* Empirically, the block counts on most SVR3 and SVR3-derived
+ systems seem to always be in terms of 512-byte blocks,
+ no matter what value f_bsize has. */
+ fsp->fsu_blocksize = 512;
+#endif
+
+#if (defined STAT_STATVFS64 || defined STAT_STATFS3_OSF1 \
+ || defined STAT_STATFS2_FRSIZE || defined STAT_STATFS2_BSIZE \
+ || defined STAT_STATFS2_FSIZE || defined STAT_STATFS4)
+
+ fsp->fsu_blocks = PROPAGATE_ALL_ONES (fsd.f_blocks);
+ fsp->fsu_bfree = PROPAGATE_ALL_ONES (fsd.f_bfree);
+ fsp->fsu_bavail = PROPAGATE_TOP_BIT (fsd.f_bavail);
+ fsp->fsu_bavail_top_bit_set = EXTRACT_TOP_BIT (fsd.f_bavail) != 0;
+ fsp->fsu_files = PROPAGATE_ALL_ONES (fsd.f_files);
+ fsp->fsu_ffree = PROPAGATE_ALL_ONES (fsd.f_ffree);
+
+#endif
+ }
+
+ (void) disk; /* avoid argument-unused warning */
+
+ return 0;
+}
+#endif /* HAVE_INFOMOUNT */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+free_my_statfs (void)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ g_clear_slist (&mc_mount_list, (GDestroyNotify) free_mount_entry);
+#endif /* HAVE_INFOMOUNT_LIST */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+init_my_statfs (void)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ free_my_statfs ();
+ mc_mount_list = read_file_system_list ();
+#endif /* HAVE_INFOMOUNT_LIST */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+my_statfs (struct my_statfs *myfs_stats, const char *path)
+{
+#ifdef HAVE_INFOMOUNT_LIST
+ size_t len = 0;
+ struct mount_entry *entry = NULL;
+ GSList *temp;
+ struct fs_usage fs_use;
+
+ for (temp = mc_mount_list; temp != NULL; temp = g_slist_next (temp))
+ {
+ struct mount_entry *me;
+ size_t i;
+
+ me = (struct mount_entry *) temp->data;
+ i = strlen (me->me_mountdir);
+ if (i > len && (strncmp (path, me->me_mountdir, i) == 0) &&
+ (entry == NULL || IS_PATH_SEP (path[i]) || path[i] == '\0'))
+ {
+ len = i;
+ entry = me;
+ }
+ }
+
+ if (entry != NULL)
+ {
+ memset (&fs_use, 0, sizeof (fs_use));
+ get_fs_usage (entry->me_mountdir, NULL, &fs_use);
+
+ myfs_stats->type = entry->me_dev;
+ myfs_stats->typename = entry->me_type;
+ myfs_stats->mpoint = entry->me_mountdir;
+ myfs_stats->mroot = entry->me_mntroot;
+ myfs_stats->device = entry->me_devname;
+ myfs_stats->avail =
+ ((uintmax_t) (getuid ()? fs_use.fsu_bavail : fs_use.fsu_bfree) *
+ fs_use.fsu_blocksize) >> 10;
+ myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
+ myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
+ }
+ else
+#endif /* HAVE_INFOMOUNT_LIST */
+
+#ifdef HAVE_INFOMOUNT_QNX
+ /*
+ ** This is the "other side" of the hack to read_file_system_list() above.
+ ** It's not the most efficient approach, but consumes less memory. It
+ ** also accommodates QNX's ability to mount filesystems on the fly.
+ */
+ struct mount_entry *entry;
+ struct fs_usage fs_use;
+
+ entry = read_file_system_list ();
+ if (entry != NULL)
+ {
+ get_fs_usage (entry->me_mountdir, NULL, &fs_use);
+
+ myfs_stats->type = entry->me_dev;
+ myfs_stats->typename = entry->me_type;
+ myfs_stats->mpoint = entry->me_mountdir;
+ myfs_stats->mroot = entry->me_mntroot;
+ myfs_stats->device = entry->me_devname;
+
+ myfs_stats->avail = ((uintmax_t) fs_use.fsu_bfree * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->total = ((uintmax_t) fs_use.fsu_blocks * fs_use.fsu_blocksize) >> 10;
+ myfs_stats->nfree = (uintmax_t) fs_use.fsu_ffree;
+ myfs_stats->nodes = (uintmax_t) fs_use.fsu_files;
+ }
+ else
+#endif /* HAVE_INFOMOUNT_QNX */
+ {
+ myfs_stats->type = 0;
+ myfs_stats->mpoint = "unknown";
+ myfs_stats->device = "unknown";
+ myfs_stats->avail = 0;
+ myfs_stats->total = 0;
+ myfs_stats->nfree = 0;
+ myfs_stats->nodes = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/mountlist.h b/src/filemanager/mountlist.h
new file mode 100644
index 0000000..f506488
--- /dev/null
+++ b/src/filemanager/mountlist.h
@@ -0,0 +1,44 @@
+/*
+ Declarations for list of mounted filesystems
+ */
+
+/** \file mountlist.h
+ * \brief Header: list of mounted filesystems
+ */
+
+#ifndef MC__MOUNTLIST_H
+#define MC__MOUNTLIST_H
+
+#include <stdint.h> /* uintmax_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Filesystem status */
+struct my_statfs
+{
+ int type;
+ char *typename;
+ const char *mpoint;
+ const char *mroot;
+ const char *device;
+ uintmax_t avail; /* in kB */
+ uintmax_t total; /* in kB */
+ uintmax_t nfree;
+ uintmax_t nodes;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void init_my_statfs (void);
+void my_statfs (struct my_statfs *myfs_stats, const char *path);
+void free_my_statfs (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__MOUNTLIST_H */
diff --git a/src/filemanager/panel.c b/src/filemanager/panel.c
new file mode 100644
index 0000000..ec1dbc3
--- /dev/null
+++ b/src/filemanager/panel.c
@@ -0,0 +1,5428 @@
+/*
+ Panel managing.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Miguel de Icaza, 1995
+ Timur Bakeyev, 1997, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file panel.c
+ * \brief Source: panel managin module
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/skin.h"
+#include "lib/strescape.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/unixcompat.h"
+#include "lib/search.h"
+#include "lib/timefmt.h" /* file_date() */
+#include "lib/util.h"
+#include "lib/widget.h"
+#ifdef HAVE_CHARSET
+#include "lib/charsets.h" /* get_codepage_id () */
+#endif
+#include "lib/event.h"
+
+#include "src/setup.h" /* For loading/saving panel options */
+#include "src/execute.h"
+#ifdef HAVE_CHARSET
+#include "src/selcodepage.h" /* select_charset (), SELECT_CHARSET_NO_TRANSLATE */
+#endif
+#include "src/keymap.h" /* global_keymap_t */
+#include "src/history.h"
+#ifdef ENABLE_SUBSHELL
+#include "src/subshell/subshell.h" /* do_subshell_chdir() */
+#endif
+
+#include "src/usermenu.h"
+
+#include "dir.h"
+#include "boxes.h"
+#include "tree.h"
+#include "ext.h" /* regexp_command */
+#include "layout.h" /* Most layout variables are here */
+#include "cmd.h"
+#include "command.h" /* cmdline */
+#include "filemanager.h"
+#include "mountlist.h" /* my_statfs */
+#include "cd.h" /* cd_error_message() */
+
+#include "panel.h"
+
+/*** global variables ****************************************************************************/
+
+/* The hook list for the select file function */
+hook_t *select_file_hook = NULL;
+
+mc_fhl_t *mc_filehighlight = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+typedef enum
+{
+ FATTR_NORMAL = 0,
+ FATTR_CURRENT,
+ FATTR_MARKED,
+ FATTR_MARKED_CURRENT,
+ FATTR_STATUS
+} file_attr_t;
+
+/* select/unselect dialog results */
+#define SELECT_RESET ((mc_search_t *)(-1))
+#define SELECT_ERROR ((mc_search_t *)(-2))
+
+/* mouse position relative to file list */
+#define MOUSE_UPPER_FILE_LIST (-1)
+#define MOUSE_BELOW_FILE_LIST (-2)
+#define MOUSE_AFTER_LAST_FILE (-3)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef enum
+{
+ MARK_DONT_MOVE = 0,
+ MARK_DOWN = 1,
+ MARK_FORCE_DOWN = 2,
+ MARK_FORCE_UP = 3
+} mark_act_t;
+
+/*
+ * This describes a format item. The parse_display_format routine parses
+ * the user specified format and creates a linked list of format_item_t structures.
+ */
+typedef struct format_item_t
+{
+ int requested_field_len;
+ int field_len;
+ align_crt_t just_mode;
+ gboolean expand;
+ const char *(*string_fn) (file_entry_t *, int len);
+ char *title;
+ const char *id;
+} format_item_t;
+
+/* File name scroll states */
+typedef enum
+{
+ FILENAME_NOSCROLL = 1,
+ FILENAME_SCROLL_LEFT = 2,
+ FILENAME_SCROLL_RIGHT = 4
+} filename_scroll_flag_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static const char *string_file_name (file_entry_t * fe, int len);
+static const char *string_file_size (file_entry_t * fe, int len);
+static const char *string_file_size_brief (file_entry_t * fe, int len);
+static const char *string_file_type (file_entry_t * fe, int len);
+static const char *string_file_mtime (file_entry_t * fe, int len);
+static const char *string_file_atime (file_entry_t * fe, int len);
+static const char *string_file_ctime (file_entry_t * fe, int len);
+static const char *string_file_permission (file_entry_t * fe, int len);
+static const char *string_file_perm_octal (file_entry_t * fe, int len);
+static const char *string_file_nlinks (file_entry_t * fe, int len);
+static const char *string_inode (file_entry_t * fe, int len);
+static const char *string_file_nuid (file_entry_t * fe, int len);
+static const char *string_file_ngid (file_entry_t * fe, int len);
+static const char *string_file_owner (file_entry_t * fe, int len);
+static const char *string_file_group (file_entry_t * fe, int len);
+static const char *string_marked (file_entry_t * fe, int len);
+static const char *string_space (file_entry_t * fe, int len);
+static const char *string_dot (file_entry_t * fe, int len);
+
+/*** file scope variables ************************************************************************/
+
+/* *INDENT-OFF* */
+static panel_field_t panel_fields[] = {
+ {
+ "unsorted", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'unsorted' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|u"),
+ N_("&Unsorted"), TRUE, FALSE,
+ string_file_name,
+ (GCompareFunc) unsorted
+ }
+ ,
+ {
+ "name", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'name' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|n"),
+ N_("&Name"), TRUE, TRUE,
+ string_file_name,
+ (GCompareFunc) sort_name
+ }
+ ,
+ {
+ "version", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'version' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|v"),
+ N_("&Version"), TRUE, FALSE,
+ string_file_name,
+ (GCompareFunc) sort_vers
+ }
+ ,
+ {
+ "extension", 12, TRUE, J_LEFT_FIT,
+ /* TRANSLATORS: one single character to represent 'extension' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|e"),
+ N_("E&xtension"), TRUE, FALSE,
+ string_file_name, /* TODO: string_file_ext */
+ (GCompareFunc) sort_ext
+ }
+ ,
+ {
+ "size", 7, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'size' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|s"),
+ N_("&Size"), TRUE, TRUE,
+ string_file_size,
+ (GCompareFunc) sort_size
+ }
+ ,
+ {
+ "bsize", 7, FALSE, J_RIGHT,
+ "",
+ N_("Block Size"), FALSE, FALSE,
+ string_file_size_brief,
+ (GCompareFunc) sort_size
+ }
+ ,
+ {
+ "type", 1, FALSE, J_LEFT,
+ "",
+ "", FALSE, TRUE,
+ string_file_type,
+ NULL
+ }
+ ,
+ {
+ "mtime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Modify time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|m"),
+ N_("&Modify time"), TRUE, TRUE,
+ string_file_mtime,
+ (GCompareFunc) sort_time
+ }
+ ,
+ {
+ "atime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Access time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|a"),
+ N_("&Access time"), TRUE, TRUE,
+ string_file_atime,
+ (GCompareFunc) sort_atime
+ }
+ ,
+ {
+ "ctime", 12, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'Change time' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|h"),
+ N_("C&hange time"), TRUE, TRUE,
+ string_file_ctime,
+ (GCompareFunc) sort_ctime
+ }
+ ,
+ {
+ "perm", 10, FALSE, J_LEFT,
+ "",
+ N_("Permission"), FALSE, TRUE,
+ string_file_permission,
+ NULL
+ }
+ ,
+ {
+ "mode", 6, FALSE, J_RIGHT,
+ "",
+ N_("Perm"), FALSE, TRUE,
+ string_file_perm_octal,
+ NULL
+ }
+ ,
+ {
+ "nlink", 2, FALSE, J_RIGHT,
+ "",
+ N_("Nl"), FALSE, TRUE,
+ string_file_nlinks, NULL
+ }
+ ,
+ {
+ "inode", 5, FALSE, J_RIGHT,
+ /* TRANSLATORS: one single character to represent 'inode' sort mode */
+ /* TRANSLATORS: no need to translate 'sort', it's just a context prefix */
+ N_("sort|i"),
+ N_("&Inode"), TRUE, TRUE,
+ string_inode,
+ (GCompareFunc) sort_inode
+ }
+ ,
+ {
+ "nuid", 5, FALSE, J_RIGHT,
+ "",
+ N_("UID"), FALSE, FALSE,
+ string_file_nuid,
+ NULL
+ }
+ ,
+ {
+ "ngid", 5, FALSE, J_RIGHT,
+ "",
+ N_("GID"), FALSE, FALSE,
+ string_file_ngid,
+ NULL
+ }
+ ,
+ {
+ "owner", 8, FALSE, J_LEFT_FIT,
+ "",
+ N_("Owner"), FALSE, TRUE,
+ string_file_owner,
+ NULL
+ }
+ ,
+ {
+ "group", 8, FALSE, J_LEFT_FIT,
+ "",
+ N_("Group"), FALSE, TRUE,
+ string_file_group,
+ NULL
+ }
+ ,
+ {
+ "mark", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ string_marked,
+ NULL
+ }
+ ,
+ {
+ "|", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ NULL,
+ NULL
+ }
+ ,
+ {
+ "space", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, TRUE,
+ string_space,
+ NULL
+ }
+ ,
+ {
+ "dot", 1, FALSE, J_RIGHT,
+ "",
+ " ", FALSE, FALSE,
+ string_dot,
+ NULL
+ }
+ ,
+ {
+ NULL, 0, FALSE, J_RIGHT, NULL, NULL, FALSE, FALSE, NULL, NULL
+ }
+};
+/* *INDENT-ON* */
+
+static char *panel_sort_up_char = NULL;
+static char *panel_sort_down_char = NULL;
+
+static char *panel_hiddenfiles_show_char = NULL;
+static char *panel_hiddenfiles_hide_char = NULL;
+static char *panel_history_prev_item_char = NULL;
+static char *panel_history_next_item_char = NULL;
+static char *panel_history_show_list_char = NULL;
+static char *panel_filename_scroll_left_char = NULL;
+static char *panel_filename_scroll_right_char = NULL;
+
+/* Panel that selection started */
+static WPanel *mouse_mark_panel = NULL;
+
+static gboolean mouse_marking = FALSE;
+static int state_mark = 0;
+
+static GString *string_file_name_buffer;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static panelized_descr_t *
+panelized_descr_new (void)
+{
+ panelized_descr_t *p;
+
+ p = g_new0 (panelized_descr_t, 1);
+ p->list.len = -1;
+
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelized_descr_free (panelized_descr_t * p)
+{
+ if (p != NULL)
+ {
+ dir_list_free_list (&p->list);
+ vfs_path_free (p->root_vpath, TRUE);
+ g_free (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_colors (const WPanel * panel)
+{
+ (void) panel;
+
+ tty_set_normal_attrs ();
+ tty_setcolor (NORMAL_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Delete format_item_t object */
+
+static void
+format_item_free (format_item_t * format)
+{
+ g_free (format->title);
+ g_free (format);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Extract the number of available lines in a panel */
+
+static int
+panel_lines (const WPanel * p)
+{
+ /* 3 lines are: top frame, column header, button frame */
+ return (CONST_WIDGET (p)->rect.lines - 3 - (panels_options.show_mini_info ? 2 : 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This code relies on the default justification!!! */
+
+static void
+add_permission_string (const char *dest, int width, file_entry_t * fe, file_attr_t attr, int color,
+ gboolean is_octal)
+{
+ int i, r, l;
+
+ l = get_user_permissions (&fe->st);
+
+ if (is_octal)
+ {
+ /* Place of the access bit in octal mode */
+ l = width + l - 3;
+ r = l + 1;
+ }
+ else
+ {
+ /* The same to the triplet in string mode */
+ l = l * 3 + 1;
+ r = l + 3;
+ }
+
+ for (i = 0; i < width; i++)
+ {
+ if (i >= l && i < r)
+ {
+ if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
+ tty_setcolor (MARKED_SELECTED_COLOR);
+ else
+ tty_setcolor (MARKED_COLOR);
+ }
+ else if (color >= 0)
+ tty_setcolor (color);
+ else
+ tty_lowlevel_setcolor (-color);
+
+ tty_print_char (dest[i]);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** String representations of various file attributes name */
+
+static const char *
+string_file_name (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ mc_g_string_copy (string_file_name_buffer, fe->fname);
+
+ return string_file_name_buffer->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned int
+ilog10 (dev_t n)
+{
+ unsigned int digits = 0;
+
+ do
+ {
+ digits++;
+ n /= 10;
+ }
+ while (n != 0);
+
+ return digits;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+format_device_number (char *buf, size_t bufsize, dev_t dev)
+{
+ dev_t major_dev, minor_dev;
+ unsigned int major_digits, minor_digits;
+
+ major_dev = major (dev);
+ major_digits = ilog10 (major_dev);
+
+ minor_dev = minor (dev);
+ minor_digits = ilog10 (minor_dev);
+
+ g_assert (bufsize >= 1);
+
+ if (major_digits + 1 + minor_digits + 1 <= bufsize)
+ g_snprintf (buf, bufsize, "%lu,%lu", (unsigned long) major_dev, (unsigned long) minor_dev);
+ else
+ g_strlcpy (buf, _("[dev]"), bufsize);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** size */
+
+static const char *
+string_file_size (file_entry_t * fe, int len)
+{
+ static char buffer[BUF_TINY];
+
+ /* Don't ever show size of ".." since we don't calculate it */
+ if (DIR_IS_DOTDOT (fe->fname->str))
+ return _("UP--DIR");
+
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ if (S_ISBLK (fe->st.st_mode) || S_ISCHR (fe->st.st_mode))
+ format_device_number (buffer, len + 1, fe->st.st_rdev);
+ else
+#endif
+ size_trunc_len (buffer, (unsigned int) len, fe->st.st_size, 0, panels_options.kilobyte_si);
+
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** bsize */
+
+static const char *
+string_file_size_brief (file_entry_t * fe, int len)
+{
+ if (S_ISLNK (fe->st.st_mode) && !link_isdir (fe))
+ return _("SYMLINK");
+
+ if ((S_ISDIR (fe->st.st_mode) || link_isdir (fe)) && !DIR_IS_DOTDOT (fe->fname->str))
+ return _("SUB-DIR");
+
+ return string_file_size (fe, len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This functions return a string representation of a file entry type */
+
+static const char *
+string_file_type (file_entry_t * fe, int len)
+{
+ static char buffer[2];
+
+ (void) len;
+
+ if (S_ISDIR (fe->st.st_mode))
+ buffer[0] = PATH_SEP;
+ else if (S_ISLNK (fe->st.st_mode))
+ {
+ if (link_isdir (fe))
+ buffer[0] = '~';
+ else if (fe->f.stale_link != 0)
+ buffer[0] = '!';
+ else
+ buffer[0] = '@';
+ }
+ else if (S_ISCHR (fe->st.st_mode))
+ buffer[0] = '-';
+ else if (S_ISSOCK (fe->st.st_mode))
+ buffer[0] = '=';
+ else if (S_ISDOOR (fe->st.st_mode))
+ buffer[0] = '>';
+ else if (S_ISBLK (fe->st.st_mode))
+ buffer[0] = '+';
+ else if (S_ISFIFO (fe->st.st_mode))
+ buffer[0] = '|';
+ else if (S_ISNAM (fe->st.st_mode))
+ buffer[0] = '#';
+ else if (!S_ISREG (fe->st.st_mode))
+ buffer[0] = '?'; /* non-regular of unknown kind */
+ else if (is_exe (fe->st.st_mode))
+ buffer[0] = '*';
+ else
+ buffer[0] = ' ';
+ buffer[1] = '\0';
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mtime */
+
+static const char *
+string_file_mtime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_mtime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** atime */
+
+static const char *
+string_file_atime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_atime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** ctime */
+
+static const char *
+string_file_ctime (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return file_date (fe->st.st_ctime);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** perm */
+
+static const char *
+string_file_permission (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return string_perm (fe->st.st_mode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mode */
+
+static const char *
+string_file_perm_octal (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "0%06lo", (unsigned long) fe->st.st_mode);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** nlink */
+
+static const char *
+string_file_nlinks (file_entry_t * fe, int len)
+{
+ static char buffer[BUF_TINY];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%16d", (int) fe->st.st_nlink);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** inode */
+
+static const char *
+string_inode (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_ino);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** nuid */
+
+static const char *
+string_file_nuid (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_uid);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** ngid */
+
+static const char *
+string_file_ngid (file_entry_t * fe, int len)
+{
+ static char buffer[10];
+
+ (void) len;
+
+ g_snprintf (buffer, sizeof (buffer), "%lu", (unsigned long) fe->st.st_gid);
+ return buffer;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** owner */
+
+static const char *
+string_file_owner (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return get_owner (fe->st.st_uid);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** group */
+
+static const char *
+string_file_group (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return get_group (fe->st.st_gid);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** mark */
+
+static const char *
+string_marked (file_entry_t * fe, int len)
+{
+ (void) len;
+
+ return fe->f.marked != 0 ? "*" : " ";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** space */
+
+static const char *
+string_space (file_entry_t * fe, int len)
+{
+ (void) fe;
+ (void) len;
+
+ return " ";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** dot */
+
+static const char *
+string_dot (file_entry_t * fe, int len)
+{
+ (void) fe;
+ (void) len;
+
+ return ".";
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+file_compute_color (file_attr_t attr, file_entry_t * fe)
+{
+ switch (attr)
+ {
+ case FATTR_CURRENT:
+ return (SELECTED_COLOR);
+ case FATTR_MARKED:
+ return (MARKED_COLOR);
+ case FATTR_MARKED_CURRENT:
+ return (MARKED_SELECTED_COLOR);
+ case FATTR_STATUS:
+ return (NORMAL_COLOR);
+ case FATTR_NORMAL:
+ default:
+ if (!panels_options.filetype_mode)
+ return (NORMAL_COLOR);
+ }
+
+ return mc_fhl_get_color (mc_filehighlight, fe);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns the number of items in the given panel */
+
+static int
+panel_items (const WPanel * p)
+{
+ return panel_lines (p) * p->list_cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Formats the file number file_index of panel in the buffer dest */
+
+static filename_scroll_flag_t
+format_file (WPanel * panel, int file_index, int width, file_attr_t attr, gboolean isstatus,
+ int *field_length)
+{
+ int color = NORMAL_COLOR;
+ int length = 0;
+ GSList *format, *home;
+ file_entry_t *fe = NULL;
+ filename_scroll_flag_t res = FILENAME_NOSCROLL;
+
+ *field_length = 0;
+
+ if (file_index < panel->dir.len)
+ {
+ fe = &panel->dir.list[file_index];
+ color = file_compute_color (attr, fe);
+ }
+
+ home = isstatus ? panel->status_format : panel->format;
+
+ for (format = home; format != NULL && length != width; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ if (fi->string_fn != NULL)
+ {
+ const char *txt = " ";
+ int len, perm = 0;
+ const char *prepared_text;
+ int name_offset = 0;
+
+ if (fe != NULL)
+ txt = fi->string_fn (fe, fi->field_len);
+
+ len = fi->field_len;
+ if (len + length > width)
+ len = width - length;
+ if (len <= 0)
+ break;
+
+ if (!isstatus && panel->content_shift > -1 && strcmp (fi->id, "name") == 0)
+ {
+ int str_len;
+ int i;
+
+ *field_length = len + 1;
+
+ str_len = str_length (txt);
+ i = MAX (0, str_len - len);
+ panel->max_shift = MAX (panel->max_shift, i);
+ i = MIN (panel->content_shift, i);
+
+ if (i > -1)
+ {
+ name_offset = str_offset_to_pos (txt, i);
+ if (str_len > len)
+ {
+ res = FILENAME_SCROLL_LEFT;
+ if (str_length (txt + name_offset) > len)
+ res |= FILENAME_SCROLL_RIGHT;
+ }
+ }
+ }
+
+ if (panels_options.permission_mode)
+ {
+ if (strcmp (fi->id, "perm") == 0)
+ perm = 1;
+ else if (strcmp (fi->id, "mode") == 0)
+ perm = 2;
+ }
+
+ if (color >= 0)
+ tty_setcolor (color);
+ else
+ tty_lowlevel_setcolor (-color);
+
+ if (!isstatus && panel->content_shift > -1)
+ prepared_text = str_fit_to_term (txt + name_offset, len, HIDE_FIT (fi->just_mode));
+ else
+ prepared_text = str_fit_to_term (txt, len, fi->just_mode);
+
+ if (perm != 0 && fe != NULL)
+ add_permission_string (prepared_text, fi->field_len, fe, attr, color, perm != 1);
+ else
+ tty_print_string (prepared_text);
+
+ length += len;
+ }
+ else
+ {
+ if (attr == FATTR_CURRENT || attr == FATTR_MARKED_CURRENT)
+ tty_setcolor (SELECTED_COLOR);
+ else
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ length++;
+ }
+ }
+
+ if (length < width)
+ {
+ int y, x;
+
+ tty_getyx (&y, &x);
+ tty_draw_hline (y, x, ' ', width - length);
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+repaint_file (WPanel * panel, int file_index, file_attr_t attr)
+{
+ Widget *w = WIDGET (panel);
+
+ int nth_column = 0;
+ int width;
+ int offset = 0;
+ filename_scroll_flag_t ret_frm;
+ int ypos = 0;
+ gboolean panel_is_split;
+ int fln = 0;
+
+ panel_is_split = panel->list_cols > 1;
+ width = w->rect.cols - 2;
+
+ if (panel_is_split)
+ {
+ nth_column = (file_index - panel->top) / panel_lines (panel);
+ width /= panel->list_cols;
+
+ offset = width * nth_column;
+
+ if (nth_column + 1 >= panel->list_cols)
+ width = w->rect.cols - offset - 2;
+ }
+
+ /* Nothing to paint */
+ if (width <= 0)
+ return;
+
+ ypos = file_index - panel->top;
+
+ if (panel_is_split)
+ ypos %= panel_lines (panel);
+
+ ypos += 2; /* top frame and header */
+ widget_gotoyx (w, ypos, offset + 1);
+
+ ret_frm = format_file (panel, file_index, width, attr, FALSE, &fln);
+
+ if (panel_is_split && nth_column + 1 < panel->list_cols)
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+
+ if (ret_frm != FILENAME_NOSCROLL)
+ {
+ if (!panel_is_split && fln > 0)
+ {
+ if (panel->list_format != list_long)
+ width = fln;
+ else
+ {
+ offset = width - fln + 1;
+ width = fln - 1;
+ }
+ }
+
+ widget_gotoyx (w, ypos, offset);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (panel_filename_scroll_left_char);
+
+ if ((ret_frm & FILENAME_SCROLL_RIGHT) != 0)
+ {
+ offset += width;
+ if (nth_column + 1 >= panel->list_cols)
+ offset++;
+
+ widget_gotoyx (w, ypos, offset);
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (panel_filename_scroll_right_char);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+repaint_status (WPanel * panel)
+{
+ int width;
+
+ width = WIDGET (panel)->rect.cols - 2;
+ if (width > 0)
+ {
+ int fln = 0;
+
+ (void) format_file (panel, panel->current, width, FATTR_STATUS, TRUE, &fln);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+display_mini_info (WPanel * panel)
+{
+ Widget *w = WIDGET (panel);
+ const file_entry_t *fe;
+
+ if (!panels_options.show_mini_info || panel->current < 0)
+ return;
+
+ widget_gotoyx (w, panel_lines (panel) + 3, 1);
+
+ if (panel->quick_search.active)
+ {
+ tty_setcolor (INPUT_COLOR);
+ tty_print_char ('/');
+ tty_print_string (str_fit_to_term
+ (panel->quick_search.buffer->str, w->rect.cols - 3, J_LEFT));
+ return;
+ }
+
+ /* Status resolves links and show them */
+ set_colors (panel);
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISLNK (fe->st.st_mode))
+ {
+ char link_target[MC_MAXPATHLEN];
+ vfs_path_t *lc_link_vpath;
+ int len;
+
+ lc_link_vpath = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
+ len = mc_readlink (lc_link_vpath, link_target, MC_MAXPATHLEN - 1);
+ vfs_path_free (lc_link_vpath, TRUE);
+ if (len > 0)
+ {
+ link_target[len] = 0;
+ tty_print_string ("-> ");
+ tty_print_string (str_fit_to_term (link_target, w->rect.cols - 5, J_LEFT_FIT));
+ }
+ else
+ tty_print_string (str_fit_to_term (_("<readlink failed>"), w->rect.cols - 2, J_LEFT));
+ }
+ else if (DIR_IS_DOTDOT (fe->fname->str))
+ {
+ /* FIXME:
+ * while loading directory (dir_list_load() and dir_list_reload()),
+ * the actual stat info about ".." directory isn't got;
+ * so just don't display incorrect info about ".." directory */
+ tty_print_string (str_fit_to_term (_("UP--DIR"), w->rect.cols - 2, J_LEFT));
+ }
+ else
+ /* Default behavior */
+ repaint_status (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+paint_dir (WPanel * panel)
+{
+ int i;
+ int items; /* Number of items */
+
+ items = panel_items (panel);
+ /* reset max len of filename because we have the new max length for the new file list */
+ panel->max_shift = -1;
+
+ for (i = 0; i < items; i++)
+ {
+ file_attr_t attr = FATTR_NORMAL; /* Color value of the line */
+ int n;
+ gboolean marked;
+
+ n = i + panel->top;
+ marked = (panel->dir.list[n].f.marked != 0);
+
+ if (n < panel->dir.len)
+ {
+ if (panel->current == n && panel->active)
+ attr = marked ? FATTR_MARKED_CURRENT : FATTR_CURRENT;
+ else if (marked)
+ attr = FATTR_MARKED;
+ }
+
+ repaint_file (panel, n, attr);
+ }
+
+ tty_set_normal_attrs ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+display_total_marked_size (const WPanel * panel, int y, int x, gboolean size_only)
+{
+ const Widget *w = CONST_WIDGET (panel);
+
+ char buffer[BUF_SMALL], b_bytes[BUF_SMALL];
+ const char *buf;
+ int cols;
+
+ if (panel->marked <= 0)
+ return;
+
+ buf = size_only ? b_bytes : buffer;
+ cols = w->rect.cols - 2;
+
+ g_strlcpy (b_bytes, size_trunc_sep (panel->total, panels_options.kilobyte_si),
+ sizeof (b_bytes));
+
+ if (!size_only)
+ g_snprintf (buffer, sizeof (buffer),
+ ngettext ("%s in %d file", "%s in %d files", panel->marked),
+ b_bytes, panel->marked);
+
+ /* don't forget spaces around buffer content */
+ buf = str_trunc (buf, cols - 4);
+
+ if (x < 0)
+ /* center in panel */
+ x = (w->rect.cols - str_term_width1 (buf)) / 2 - 1;
+
+ /*
+ * y == panel_lines (panel) + 2 for mini_info_separator
+ * y == w->lines - 1 for panel bottom frame
+ */
+ widget_gotoyx (w, y, x);
+ tty_setcolor (MARKED_COLOR);
+ tty_printf (" %s ", buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mini_info_separator (const WPanel * panel)
+{
+ if (panels_options.show_mini_info)
+ {
+ const Widget *w = CONST_WIDGET (panel);
+ int y;
+
+ y = panel_lines (panel) + 2;
+
+ tty_setcolor (NORMAL_COLOR);
+ tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+ /* Status displays total marked size.
+ * Centered in panel, full format. */
+ display_total_marked_size (panel, y, -1, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_free_space (const WPanel * panel)
+{
+ /* Used to figure out how many free space we have */
+ static struct my_statfs myfs_stats;
+ /* Old current working directory for displaying free space */
+ static char *old_cwd = NULL;
+
+ /* Don't try to stat non-local fs */
+ if (!vfs_file_is_local (panel->cwd_vpath) || !free_space)
+ return;
+
+ if (old_cwd == NULL || strcmp (old_cwd, vfs_path_as_str (panel->cwd_vpath)) != 0)
+ {
+ char rpath[PATH_MAX];
+
+ init_my_statfs ();
+ g_free (old_cwd);
+ old_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
+
+ if (mc_realpath (old_cwd, rpath) == NULL)
+ return;
+
+ my_statfs (&myfs_stats, rpath);
+ }
+
+ if (myfs_stats.avail != 0 || myfs_stats.total != 0)
+ {
+ const Widget *w = CONST_WIDGET (panel);
+ char buffer1[6], buffer2[6], tmp[BUF_SMALL];
+
+ size_trunc_len (buffer1, sizeof (buffer1) - 1, myfs_stats.avail, 1,
+ panels_options.kilobyte_si);
+ size_trunc_len (buffer2, sizeof (buffer2) - 1, myfs_stats.total, 1,
+ panels_options.kilobyte_si);
+ g_snprintf (tmp, sizeof (tmp), " %s / %s (%d%%) ", buffer1, buffer2,
+ myfs_stats.total == 0 ? 0 :
+ (int) (100 * (long double) myfs_stats.avail / myfs_stats.total));
+ widget_gotoyx (w, w->rect.lines - 1, w->rect.cols - 2 - (int) strlen (tmp));
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_string (tmp);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Prepare path string for showing in panel's header.
+ * Passwords will removed, also home dir will replaced by ~
+ *
+ * @param panel WPanel object
+ *
+ * @return newly allocated string.
+ */
+
+static char *
+panel_correct_path_to_show (const WPanel * panel)
+{
+ vfs_path_t *last_vpath;
+ const vfs_path_element_t *path_element;
+ char *return_path;
+ int elements_count;
+
+ elements_count = vfs_path_elements_count (panel->cwd_vpath);
+
+ /* get last path element */
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (panel->cwd_vpath, -1));
+
+ if (elements_count > 1 && (strcmp (path_element->class->name, "cpiofs") == 0 ||
+ strcmp (path_element->class->name, "extfs") == 0 ||
+ strcmp (path_element->class->name, "tarfs") == 0))
+ {
+ const char *archive_name;
+ const vfs_path_element_t *prev_path_element;
+
+ /* get previous path element for catching archive name */
+ prev_path_element = vfs_path_get_by_index (panel->cwd_vpath, -2);
+ archive_name = strrchr (prev_path_element->path, PATH_SEP);
+ if (archive_name != NULL)
+ last_vpath = vfs_path_from_str_flags (archive_name + 1, VPF_NO_CANON);
+ else
+ {
+ last_vpath = vfs_path_from_str_flags (prev_path_element->path, VPF_NO_CANON);
+ last_vpath->relative = TRUE;
+ }
+ }
+ else
+ last_vpath = vfs_path_new (TRUE);
+
+ vfs_path_add_element (last_vpath, path_element);
+ return_path =
+ vfs_path_to_str_flags (last_vpath, 0,
+ VPF_STRIP_HOME | VPF_STRIP_PASSWORD | VPF_HIDE_CHARSET);
+ vfs_path_free (last_vpath, TRUE);
+
+ return return_path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get Current path element encoding
+ *
+ * @param panel WPanel object
+ *
+ * @return newly allocated string or NULL if path charset is same as system charset
+ */
+
+#ifdef HAVE_CHARSET
+static char *
+panel_get_encoding_info_str (const WPanel * panel)
+{
+ char *ret_str = NULL;
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ ret_str = g_strdup_printf ("[%s]", path_element->encoding);
+
+ return ret_str;
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_dir (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+ gchar *tmp;
+
+ set_colors (panel);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ if (panels_options.show_mini_info)
+ {
+ int y;
+
+ y = panel_lines (panel) + 2;
+
+ widget_gotoyx (w, y, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, y, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+
+ widget_gotoyx (w, 0, 1);
+ tty_print_string (panel_history_prev_item_char);
+
+ tmp = panels_options.show_dot_files ? panel_hiddenfiles_show_char : panel_hiddenfiles_hide_char;
+ tmp = g_strdup_printf ("%s[%s]%s", tmp, panel_history_show_list_char,
+ panel_history_next_item_char);
+
+ widget_gotoyx (w, 0, w->rect.cols - 6);
+ tty_print_string (tmp);
+
+ g_free (tmp);
+
+ widget_gotoyx (w, 0, 3);
+
+ if (panel->is_panelized)
+ tty_printf (" %s ", _("Panelize"));
+#ifdef HAVE_CHARSET
+ else
+ {
+ tmp = panel_get_encoding_info_str (panel);
+ if (tmp != NULL)
+ {
+ tty_printf ("%s", tmp);
+ widget_gotoyx (w, 0, 3 + strlen (tmp));
+ g_free (tmp);
+ }
+ }
+#endif
+
+ if (panel->active)
+ tty_setcolor (REVERSE_COLOR);
+
+ tmp = panel_correct_path_to_show (panel);
+ tty_printf (" %s ", str_term_trim (tmp, MIN (MAX (w->rect.cols - 12, 0), w->rect.cols)));
+ g_free (tmp);
+
+ if (!panels_options.show_mini_info)
+ {
+ if (panel->marked == 0)
+ {
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ /* Show size of curret file in the bottom of panel */
+ if (S_ISREG (fe->st.st_mode))
+ {
+ char buffer[BUF_SMALL];
+
+ g_snprintf (buffer, sizeof (buffer), " %s ",
+ size_trunc_sep (fe->st.st_size, panels_options.kilobyte_si));
+ tty_setcolor (NORMAL_COLOR);
+ widget_gotoyx (w, w->rect.lines - 1, 4);
+ tty_print_string (buffer);
+ }
+ }
+ else
+ {
+ /* Show total size of marked files
+ * In the bottom of panel, display size only. */
+ display_total_marked_size (panel, w->rect.lines - 1, 2, TRUE);
+ }
+ }
+
+ show_free_space (panel);
+
+ if (panel->active)
+ tty_set_normal_attrs ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+adjust_top_file (WPanel * panel)
+{
+ int items;
+
+ /* Update panel->current to avoid out of range in panel->dir.list[panel->current]
+ * when panel is redrawing when directory is reloading, for example in path:
+ * dir_list_reload() -> mc_refresh() -> dialog_change_screen_size() ->
+ * midnight_callback (MSG_RESIZE) -> setup_panels() -> panel_callback(MSG_DRAW) ->
+ * display_mini_info()
+ */
+ panel->current = CLAMP (panel->current, 0, panel->dir.len - 1);
+
+ items = panel_items (panel);
+
+ if (panel->dir.len <= items)
+ {
+ /* If all files fit, show them all. */
+ panel->top = 0;
+ }
+ else
+ {
+ int i;
+
+ /* top_file has to be in the range [current-items+1, current] so that
+ the current file is visible.
+ top_file should be in the range [0, count-items] so that there's
+ no empty space wasted.
+ Within these ranges, adjust it by as little as possible. */
+
+ if (panel->top < 0)
+ panel->top = 0;
+
+ i = panel->current - items + 1;
+ if (panel->top < i)
+ panel->top = i;
+
+ i = panel->dir.len - items;
+ if (panel->top > i)
+ panel->top = i;
+
+ if (panel->top > panel->current)
+ panel->top = panel->current;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** add "#enc:encoding" to end of path */
+/* if path ends width a previous #enc:, only encoding is changed no additional
+ * #enc: is appended
+ * return new string
+ */
+
+static char *
+panel_save_name (WPanel * panel)
+{
+ /* If the program is shutting down */
+ if ((mc_global.midnight_shutdown && auto_save_setup) || saving_setup)
+ return g_strdup (panel->name);
+
+ return g_strconcat ("Temporal:", panel->name, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_add (WPanel * panel, const vfs_path_t * vpath)
+{
+ char *tmp;
+
+ tmp = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_PASSWORD);
+ panel->dir_history.list = list_append_unique (panel->dir_history.list, tmp);
+ panel->dir_history.current = panel->dir_history.list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_load" event handler */
+static gboolean
+panel_load_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WPanel *p = PANEL (init_data);
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (ev->receiver == NULL || ev->receiver == WIDGET (p))
+ {
+ if (ev->cfg != NULL)
+ p->dir_history.list = mc_config_history_load (ev->cfg, p->dir_history.name);
+ else
+ p->dir_history.list = mc_config_history_get (p->dir_history.name);
+
+ directory_history_add (p, p->cwd_vpath);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_save" event handler */
+static gboolean
+panel_save_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WPanel *p = PANEL (init_data);
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (p->dir_history.list != NULL)
+ {
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ mc_config_history_save (ev->cfg, p->dir_history.name, p->dir_history.list);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_destroy (WPanel * p)
+{
+ size_t i;
+
+ if (panels_options.auto_save_setup)
+ {
+ char *name;
+
+ name = panel_save_name (p);
+ panel_save_setup (p, name);
+ g_free (name);
+ }
+
+ panel_clean_dir (p);
+
+ /* clean history */
+ if (p->dir_history.list != NULL)
+ {
+ /* directory history is already saved before this moment */
+ p->dir_history.list = g_list_first (p->dir_history.list);
+ g_list_free_full (p->dir_history.list, g_free);
+ }
+ g_free (p->dir_history.name);
+
+ file_filter_clear (&p->filter);
+
+ g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
+ g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
+
+ g_free (p->user_format);
+ for (i = 0; i < LIST_FORMATS; i++)
+ g_free (p->user_status_format[i]);
+
+ g_free (p->name);
+
+ panelized_descr_free (p->panelized_descr);
+
+ g_string_free (p->quick_search.buffer, TRUE);
+ g_string_free (p->quick_search.prev_buffer, TRUE);
+
+ vfs_path_free (p->lwd_vpath, TRUE);
+ vfs_path_free (p->cwd_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_paint_sort_info (const WPanel * panel)
+{
+ if (*panel->sort_field->hotkey != '\0')
+ {
+ const char *sort_sign =
+ panel->sort_info.reverse ? panel_sort_up_char : panel_sort_down_char;
+ char *str;
+
+ str = g_strdup_printf ("%s%s", sort_sign, Q_ (panel->sort_field->hotkey));
+ widget_gotoyx (panel, 1, 1);
+ tty_print_string (str);
+ g_free (str);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+panel_get_title_without_hotkey (const char *title)
+{
+ static char translated_title[BUF_TINY];
+
+ if (title == NULL || title[0] == '\0')
+ translated_title[0] = '\0';
+ else
+ {
+ char *hkey;
+
+ g_snprintf (translated_title, sizeof (translated_title), "%s", _(title));
+
+ hkey = strchr (translated_title, '&');
+ if (hkey != NULL && hkey[1] != '\0')
+ memmove (hkey, hkey + 1, strlen (hkey));
+ }
+
+ return translated_title;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_print_header (const WPanel * panel)
+{
+ const Widget *w = CONST_WIDGET (panel);
+
+ int y, x;
+ int i;
+ GString *format_txt;
+
+ widget_gotoyx (w, 1, 1);
+ tty_getyx (&y, &x);
+ tty_setcolor (NORMAL_COLOR);
+ tty_draw_hline (y, x, ' ', w->rect.cols - 2);
+
+ format_txt = g_string_new ("");
+
+ for (i = 0; i < panel->list_cols; i++)
+ {
+ GSList *format;
+
+ for (format = panel->format; format != NULL; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ if (fi->string_fn != NULL)
+ {
+ g_string_set_size (format_txt, 0);
+
+ if (panel->list_format == list_long && strcmp (fi->id, panel->sort_field->id) == 0)
+ g_string_append (format_txt,
+ panel->sort_info.reverse
+ ? panel_sort_up_char : panel_sort_down_char);
+
+ g_string_append (format_txt, fi->title);
+
+ if (panel->filter.handler != NULL && strcmp (fi->id, "name") == 0)
+ {
+ g_string_append (format_txt, " [");
+ g_string_append (format_txt, panel->filter.value);
+ g_string_append (format_txt, "]");
+ }
+
+ tty_setcolor (HEADER_COLOR);
+ tty_print_string (str_fit_to_term (format_txt->str, fi->field_len, J_CENTER_LEFT));
+ }
+ else
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+ }
+
+ if (i < panel->list_cols - 1)
+ {
+ tty_setcolor (NORMAL_COLOR);
+ tty_print_one_vline (TRUE);
+ }
+ }
+
+ g_string_free (format_txt, TRUE);
+
+ if (panel->list_format != list_long)
+ panel_paint_sort_info (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+parse_panel_size (WPanel * panel, const char *format, gboolean isstatus)
+{
+ panel_display_t frame = frame_half;
+
+ format = skip_separators (format);
+
+ if (strncmp (format, "full", 4) == 0)
+ {
+ frame = frame_full;
+ format += 4;
+ }
+ else if (strncmp (format, "half", 4) == 0)
+ {
+ frame = frame_half;
+ format += 4;
+ }
+
+ if (!isstatus)
+ {
+ panel->frame_size = frame;
+ panel->list_cols = 1;
+ }
+
+ /* Now, the optional column specifier */
+ format = skip_separators (format);
+
+ if (g_ascii_isdigit (*format))
+ {
+ if (!isstatus)
+ {
+ panel->list_cols = g_ascii_digit_value (*format);
+ if (panel->list_cols < 1)
+ panel->list_cols = 1;
+ }
+
+ format++;
+ }
+
+ if (!isstatus)
+ panel_update_cols (WIDGET (panel), panel->frame_size);
+
+ return skip_separators (format);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* *INDENT-OFF* */
+/* Format is:
+
+ all := panel_format? format
+ panel_format := [full|half] [1-9]
+ format := one_format_item_t
+ | format , one_format_item_t
+
+ one_format_item_t := just format.id [opt_size]
+ just := [<=>]
+ opt_size := : size [opt_expand]
+ size := [0-9]+
+ opt_expand := +
+
+ */
+/* *INDENT-ON* */
+
+static GSList *
+parse_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus,
+ int *res_total_cols)
+{
+ GSList *home = NULL; /* The formats we return */
+ int total_cols = 0; /* Used columns by the format */
+ size_t i;
+
+ static size_t i18n_timelength = 0; /* flag: check ?Time length at startup */
+
+ *error = NULL;
+
+ if (i18n_timelength == 0)
+ {
+ i18n_timelength = i18n_checktimelength (); /* Mustn't be 0 */
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (strcmp ("time", panel_fields[i].id + 1) == 0)
+ panel_fields[i].min_size = i18n_timelength;
+ }
+
+ /*
+ * This makes sure that the panel and mini status full/half mode
+ * setting is equal
+ */
+ format = parse_panel_size (panel, format, isstatus);
+
+ while (*format != '\0')
+ { /* format can be an empty string */
+ format_item_t *darr;
+ align_crt_t justify; /* Which mode. */
+ gboolean set_justify = TRUE; /* flag: set justification mode? */
+ gboolean found = FALSE;
+ size_t klen = 0;
+
+ darr = g_new0 (format_item_t, 1);
+ home = g_slist_append (home, darr);
+
+ format = skip_separators (format);
+
+ switch (*format)
+ {
+ case '<':
+ justify = J_LEFT;
+ format = skip_separators (format + 1);
+ break;
+ case '=':
+ justify = J_CENTER;
+ format = skip_separators (format + 1);
+ break;
+ case '>':
+ justify = J_RIGHT;
+ format = skip_separators (format + 1);
+ break;
+ default:
+ justify = J_LEFT;
+ set_justify = FALSE;
+ break;
+ }
+
+ for (i = 0; !found && panel_fields[i].id != NULL; i++)
+ {
+ klen = strlen (panel_fields[i].id);
+ found = strncmp (format, panel_fields[i].id, klen) == 0;
+ }
+
+ if (found)
+ {
+ i--;
+ format += klen;
+
+ darr->requested_field_len = panel_fields[i].min_size;
+ darr->string_fn = panel_fields[i].string_fn;
+ darr->title = g_strdup (panel_get_title_without_hotkey (panel_fields[i].title_hotkey));
+ darr->id = panel_fields[i].id;
+ darr->expand = panel_fields[i].expands;
+ darr->just_mode = panel_fields[i].default_just;
+
+ if (set_justify)
+ {
+ if (IS_FIT (darr->just_mode))
+ darr->just_mode = MAKE_FIT (justify);
+ else
+ darr->just_mode = justify;
+ }
+
+ format = skip_separators (format);
+
+ /* If we have a size specifier */
+ if (*format == ':')
+ {
+ int req_length;
+
+ /* If the size was specified, we don't want
+ * auto-expansion by default
+ */
+ darr->expand = FALSE;
+ format++;
+ req_length = atoi (format);
+ darr->requested_field_len = req_length;
+
+ format = skip_numbers (format);
+
+ /* Now, if they insist on expansion */
+ if (*format == '+')
+ {
+ darr->expand = TRUE;
+ format++;
+ }
+ }
+ }
+ else
+ {
+ size_t pos;
+ char *tmp_format;
+
+ pos = strlen (format);
+ if (pos > 8)
+ pos = 8;
+
+ tmp_format = g_strndup (format, pos);
+ g_slist_free_full (home, (GDestroyNotify) format_item_free);
+ *error =
+ g_strconcat (_("Unknown tag on display format:"), " ", tmp_format, (char *) NULL);
+ g_free (tmp_format);
+
+ return NULL;
+ }
+
+ total_cols += darr->requested_field_len;
+ }
+
+ *res_total_cols = total_cols;
+ return home;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GSList *
+use_display_format (WPanel * panel, const char *format, char **error, gboolean isstatus)
+{
+#define MAX_EXPAND 4
+ int expand_top = 0; /* Max used element in expand */
+ int usable_columns; /* Usable columns in the panel */
+ int total_cols = 0;
+ GSList *darr, *home;
+
+ if (format == NULL)
+ format = DEFAULT_USER_FORMAT;
+
+ home = parse_display_format (panel, format, error, isstatus, &total_cols);
+
+ if (*error != NULL)
+ return NULL;
+
+ panel->dirty = TRUE;
+
+ usable_columns = WIDGET (panel)->rect.cols - 2;
+ /* Status needn't to be split */
+ if (!isstatus)
+ {
+ usable_columns /= panel->list_cols;
+ if (panel->list_cols > 1)
+ usable_columns--;
+ }
+
+ /* Look for the expandable fields and set field_len based on the requested field len */
+ for (darr = home; darr != NULL && expand_top < MAX_EXPAND; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ fi->field_len = fi->requested_field_len;
+ if (fi->expand)
+ expand_top++;
+ }
+
+ /* If we used more columns than the available columns, adjust that */
+ if (total_cols > usable_columns)
+ {
+ int dif;
+ int pdif = 0;
+
+ dif = total_cols - usable_columns;
+
+ while (dif != 0 && pdif != dif)
+ {
+ pdif = dif;
+
+ for (darr = home; darr != NULL; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ if (dif != 0 && fi->field_len != 1)
+ {
+ fi->field_len--;
+ dif--;
+ }
+ }
+ }
+
+ total_cols = usable_columns; /* give up, the rest should be truncated */
+ }
+
+ /* Expand the available space */
+ if (usable_columns > total_cols && expand_top != 0)
+ {
+ int i;
+ int spaces;
+
+ spaces = (usable_columns - total_cols) / expand_top;
+
+ for (i = 0, darr = home; darr != NULL && i < expand_top; darr = g_slist_next (darr))
+ {
+ format_item_t *fi = (format_item_t *) darr->data;
+
+ if (fi->expand)
+ {
+ fi->field_len += spaces;
+ if (i == 0)
+ fi->field_len += (usable_columns - total_cols) % expand_top;
+ i++;
+ }
+ }
+ }
+
+ return home;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Given the panel->view_type returns the format string to be parsed */
+
+static const char *
+panel_format (WPanel * panel)
+{
+ switch (panel->list_format)
+ {
+ case list_long:
+ return "full perm space nlink space owner space group space size space mtime space name";
+
+ case list_brief:
+ {
+ static char format[BUF_TINY];
+ int brief_cols = panel->brief_cols;
+
+ if (brief_cols < 1)
+ brief_cols = 2;
+
+ if (brief_cols > 9)
+ brief_cols = 9;
+
+ g_snprintf (format, sizeof (format), "half %d type name", brief_cols);
+ return format;
+ }
+
+ case list_user:
+ return panel->user_format;
+
+ default:
+ case list_full:
+ return "half type name | size | mtime";
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const char *
+mini_status_format (WPanel * panel)
+{
+ if (panel->user_mini_status)
+ return panel->user_status_format[panel->list_format];
+
+ switch (panel->list_format)
+ {
+ case list_long:
+ return "full perm space nlink space owner space group space size space mtime space name";
+
+ case list_brief:
+ return "half type name space bsize space perm space";
+
+ case list_full:
+ return "half type name";
+
+ default:
+ case list_user:
+ return panel->user_format;
+ }
+}
+
+/* */
+/* Panel operation commands */
+/* */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+cd_up_dir (WPanel * panel)
+{
+ vfs_path_t *up_dir;
+
+ up_dir = vfs_path_from_str ("..");
+ panel_cd (panel, up_dir, cd_exact);
+ vfs_path_free (up_dir, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Used to emulate Lynx's entering leaving a directory with the arrow keys */
+
+static cb_ret_t
+maybe_cd (WPanel * panel, gboolean move_up_dir)
+{
+ if (panels_options.navigate_with_arrows && input_is_empty (cmdline))
+ {
+ const file_entry_t *fe;
+
+ if (move_up_dir)
+ {
+ cd_up_dir (panel);
+ return MSG_HANDLED;
+ }
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (fe->fname->str);
+ panel_cd (panel, vpath, cd_exact);
+ vfs_path_free (vpath, TRUE);
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* if command line is empty then do 'cd ..' */
+static cb_ret_t
+force_maybe_cd (WPanel * panel)
+{
+ if (input_is_empty (cmdline))
+ {
+ cd_up_dir (panel);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+unselect_item (WPanel * panel)
+{
+ repaint_file (panel, panel->current,
+ panel_current_entry (panel)->f.marked != 0 ? FATTR_MARKED : FATTR_NORMAL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Select/unselect all the files like a current file by extension */
+
+static void
+panel_select_ext_cmd (WPanel * panel)
+{
+ const file_entry_t *fe;
+ GString *filename;
+ gboolean do_select;
+ char *reg_exp, *cur_file_ext;
+ mc_search_t *search;
+ int i;
+
+ fe = panel_current_entry (panel);
+
+ filename = fe->fname;
+ if (filename == NULL)
+ return;
+
+ do_select = (fe->f.marked == 0);
+
+ cur_file_ext = strutils_regex_escape (extension (filename->str));
+ if (cur_file_ext[0] != '\0')
+ reg_exp = g_strconcat ("^.*\\.", cur_file_ext, "$", (char *) NULL);
+ else
+ reg_exp = g_strdup ("^[^\\.]+$");
+
+ g_free (cur_file_ext);
+
+ search = mc_search_new (reg_exp, NULL);
+ search->search_type = MC_SEARCH_T_REGEX;
+ search->is_case_sensitive = FALSE;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ fe = &panel->dir.list[i];
+
+ if (DIR_IS_DOTDOT (fe->fname->str) || S_ISDIR (fe->st.st_mode))
+ continue;
+
+ if (!mc_search_run (search, fe->fname->str, 0, fe->fname->len, NULL))
+ continue;
+
+ do_file_mark (panel, i, do_select ? 1 : 0);
+ }
+
+ mc_search_free (search);
+ g_free (reg_exp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panel_current_at_half (const WPanel * panel)
+{
+ int lines, top;
+
+ lines = panel_lines (panel);
+
+ /* define top file of column */
+ top = panel->top;
+ if (panel->list_cols > 1)
+ top += lines * ((panel->current - top) / lines);
+
+ return (panel->current - top - lines / 2);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_down (WPanel * panel)
+{
+ int items;
+
+ if (panel->current + 1 == panel->dir.len)
+ return;
+
+ unselect_item (panel);
+ panel->current++;
+
+ items = panel_items (panel);
+
+ if (panels_options.scroll_pages && panel->current - panel->top == items)
+ {
+ /* Scroll window half screen */
+ panel->top += items / 2;
+ if (panel->top > panel->dir.len - items)
+ panel->top = panel->dir.len - items;
+ paint_dir (panel);
+ }
+ else if (panels_options.scroll_center && panel_current_at_half (panel) > 0)
+ {
+ /* Scroll window when cursor is halfway down */
+ panel->top++;
+ if (panel->top > panel->dir.len - items)
+ panel->top = panel->dir.len - items;
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_up (WPanel * panel)
+{
+ if (panel->current == 0)
+ return;
+
+ unselect_item (panel);
+ panel->current--;
+
+ if (panels_options.scroll_pages && panel->current < panel->top)
+ {
+ /* Scroll window half screen */
+ panel->top -= panel_items (panel) / 2;
+ if (panel->top < 0)
+ panel->top = 0;
+ paint_dir (panel);
+ }
+ else if (panels_options.scroll_center && panel_current_at_half (panel) < 0)
+ {
+ /* Scroll window when cursor is halfway up */
+ panel->top--;
+ if (panel->top < 0)
+ panel->top = 0;
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Changes the current by lines (may be negative) */
+
+static void
+panel_move_current (WPanel * panel, int lines)
+{
+ int new_pos;
+ gboolean adjust = FALSE;
+
+ new_pos = panel->current + lines;
+ if (new_pos >= panel->dir.len)
+ new_pos = panel->dir.len - 1;
+
+ if (new_pos < 0)
+ new_pos = 0;
+
+ unselect_item (panel);
+ panel->current = new_pos;
+
+ if (panel->current - panel->top >= panel_items (panel))
+ {
+ panel->top += lines;
+ adjust = TRUE;
+ }
+
+ if (panel->current - panel->top < 0)
+ {
+ panel->top += lines;
+ adjust = TRUE;
+ }
+
+ if (adjust)
+ {
+ if (panel->top > panel->current)
+ panel->top = panel->current;
+ if (panel->top < 0)
+ panel->top = 0;
+ paint_dir (panel);
+ }
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+move_left (WPanel * panel)
+{
+ if (panel->list_cols > 1)
+ {
+ panel_move_current (panel, -panel_lines (panel));
+ return MSG_HANDLED;
+ }
+
+ return maybe_cd (panel, TRUE); /* cd .. */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+move_right (WPanel * panel)
+{
+ if (panel->list_cols > 1)
+ {
+ panel_move_current (panel, panel_lines (panel));
+ return MSG_HANDLED;
+ }
+
+ return maybe_cd (panel, FALSE); /* cd (current) */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+prev_page (WPanel * panel)
+{
+ int items;
+
+ if (panel->current == 0 && panel->top == 0)
+ return;
+
+ unselect_item (panel);
+ items = panel_items (panel);
+ if (panel->top < items)
+ items = panel->top;
+ if (items == 0)
+ panel->current = 0;
+ else
+ panel->current -= items;
+ panel->top -= items;
+
+ select_item (panel);
+ paint_dir (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_parent_dir (WPanel * panel)
+{
+ if (!panel->is_panelized)
+ cd_up_dir (panel);
+ else
+ {
+ GString *fname;
+ const char *bname;
+ vfs_path_t *dname_vpath;
+
+ fname = panel_current_entry (panel)->fname;
+
+ if (g_path_is_absolute (fname->str))
+ fname = mc_g_string_dup (fname);
+ else
+ {
+ char *fname2;
+
+ fname2 =
+ mc_build_filename (vfs_path_as_str (panel->panelized_descr->root_vpath), fname->str,
+ (char *) NULL);
+
+ fname = g_string_new (fname2);
+ g_free (fname2);
+ }
+
+ bname = x_basename (fname->str);
+
+ if (bname == fname->str)
+ dname_vpath = vfs_path_from_str (".");
+ else
+ {
+ g_string_truncate (fname, bname - fname->str);
+ dname_vpath = vfs_path_from_str (fname->str);
+ }
+
+ panel_cd (panel, dname_vpath, cd_exact);
+ panel_set_current_by_name (panel, bname);
+
+ vfs_path_free (dname_vpath, TRUE);
+ g_string_free (fname, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+next_page (WPanel * panel)
+{
+ int items;
+
+ if (panel->current == panel->dir.len - 1)
+ return;
+
+ unselect_item (panel);
+ items = panel_items (panel);
+ if (panel->top > panel->dir.len - 2 * items)
+ items = panel->dir.len - items - panel->top;
+ if (panel->top + items < 0)
+ items = -panel->top;
+ if (items == 0)
+ panel->current = panel->dir.len - 1;
+ else
+ panel->current += items;
+ panel->top += items;
+
+ select_item (panel);
+ paint_dir (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_child_dir (WPanel * panel)
+{
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe))
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (fe->fname->str);
+ panel_cd (panel, vpath, cd_exact);
+ vfs_path_free (vpath, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_top_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_middle_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top + panel_items (panel) / 2;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+goto_bottom_file (WPanel * panel)
+{
+ unselect_item (panel);
+ panel->current = panel->top + panel_items (panel) - 1;
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_home (WPanel * panel)
+{
+ if (panel->current == 0)
+ return;
+
+ unselect_item (panel);
+
+ if (panels_options.torben_fj_mode)
+ {
+ int middle_pos;
+
+ middle_pos = panel->top + panel_items (panel) / 2;
+
+ if (panel->current > middle_pos)
+ {
+ goto_middle_file (panel);
+ return;
+ }
+ if (panel->current != panel->top)
+ {
+ goto_top_file (panel);
+ return;
+ }
+ }
+
+ panel->top = 0;
+ panel->current = 0;
+
+ paint_dir (panel);
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_end (WPanel * panel)
+{
+ if (panel->current == panel->dir.len - 1)
+ return;
+
+ unselect_item (panel);
+
+ if (panels_options.torben_fj_mode)
+ {
+ int items, middle_pos;
+
+ items = panel_items (panel);
+ middle_pos = panel->top + items / 2;
+
+ if (panel->current < middle_pos)
+ {
+ goto_middle_file (panel);
+ return;
+ }
+ if (panel->current != panel->top + items - 1)
+ {
+ goto_bottom_file (panel);
+ return;
+ }
+ }
+
+ panel->current = panel->dir.len - 1;
+ paint_dir (panel);
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_mark_file (WPanel * panel, mark_act_t do_move)
+{
+ do_file_mark (panel, panel->current, panel_current_entry (panel)->f.marked ? 0 : 1);
+
+ if ((panels_options.mark_moves_down && do_move == MARK_DOWN) || do_move == MARK_FORCE_DOWN)
+ move_down (panel);
+ else if (do_move == MARK_FORCE_UP)
+ move_up (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file (WPanel * panel)
+{
+ do_mark_file (panel, MARK_DOWN);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file_up (WPanel * panel)
+{
+ do_mark_file (panel, MARK_FORCE_UP);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+mark_file_down (WPanel * panel)
+{
+ do_mark_file (panel, MARK_FORCE_DOWN);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_file_right (WPanel * panel)
+{
+ int lines;
+
+ if (state_mark < 0)
+ state_mark = panel_current_entry (panel)->f.marked ? 0 : 1;
+
+ lines = panel_lines (panel);
+ lines = MIN (lines, panel->dir.len - panel->current - 1);
+ for (; lines != 0; lines--)
+ {
+ do_file_mark (panel, panel->current, state_mark);
+ move_down (panel);
+ }
+ do_file_mark (panel, panel->current, state_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_file_left (WPanel * panel)
+{
+ int lines;
+
+ if (state_mark < 0)
+ state_mark = panel_current_entry (panel)->f.marked ? 0 : 1;
+
+ lines = panel_lines (panel);
+ lines = MIN (lines, panel->current + 1);
+ for (; lines != 0; lines--)
+ {
+ do_file_mark (panel, panel->current, state_mark);
+ move_up (panel);
+ }
+ do_file_mark (panel, panel->current, state_mark);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_search_t *
+panel_select_unselect_files_dialog (select_flags_t * flags, const char *title,
+ const char *history_name, const char *help_section, char **str)
+{
+ gboolean files_only = (*flags & SELECT_FILES_ONLY) != 0;
+ gboolean case_sens = (*flags & SELECT_MATCH_CASE) != 0;
+ gboolean shell_patterns = (*flags & SELECT_SHELL_PATTERNS) != 0;
+
+ char *reg_exp;
+ mc_search_t *search;
+
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_INPUT (INPUT_LAST_TEXT, history_name, &reg_exp, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_FILENAMES),
+ QUICK_START_COLUMNS,
+ QUICK_CHECKBOX (N_("&Files only"), &files_only, NULL),
+ QUICK_CHECKBOX (N_("&Using shell patterns"), &shell_patterns, NULL),
+ QUICK_NEXT_COLUMN,
+ QUICK_CHECKBOX (N_("&Case sensitive"), &case_sens, NULL),
+ QUICK_STOP_COLUMNS,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, 50 };
+
+ quick_dialog_t qdlg = {
+ r, title, help_section,
+ quick_widgets, NULL, NULL
+ };
+
+ if (quick_dialog (&qdlg) == B_CANCEL)
+ return NULL;
+
+ if (*reg_exp == '\0')
+ {
+ g_free (reg_exp);
+ if (str != NULL)
+ *str = NULL;
+ return SELECT_RESET;
+ }
+
+ search = mc_search_new (reg_exp, NULL);
+ search->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ search->is_entire_line = TRUE;
+ search->is_case_sensitive = case_sens;
+
+ if (str != NULL)
+ *str = reg_exp;
+ else
+ g_free (reg_exp);
+
+ if (!mc_search_prepare (search))
+ {
+ message (D_ERROR, MSG_ERROR, _("Malformed regular expression"));
+ mc_search_free (search);
+ return SELECT_ERROR;
+ }
+
+ /* result flags */
+ *flags = 0;
+ if (case_sens)
+ *flags |= SELECT_MATCH_CASE;
+ if (files_only)
+ *flags |= SELECT_FILES_ONLY;
+ if (shell_patterns)
+ *flags |= SELECT_SHELL_PATTERNS;
+
+ return search;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_unselect_files (WPanel * panel, const char *title, const char *history_name,
+ const char *help_section, gboolean do_select)
+{
+ mc_search_t *search;
+ gboolean files_only;
+ int i;
+
+ search = panel_select_unselect_files_dialog (&panels_options.select_flags, title, history_name,
+ help_section, NULL);
+ if (search == NULL || search == SELECT_RESET || search == SELECT_ERROR)
+ return;
+
+ files_only = (panels_options.select_flags & SELECT_FILES_ONLY) != 0;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ if (DIR_IS_DOTDOT (panel->dir.list[i].fname->str))
+ continue;
+ if (S_ISDIR (panel->dir.list[i].st.st_mode) && files_only)
+ continue;
+
+ if (mc_search_run
+ (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
+ do_file_mark (panel, i, do_select ? 1 : 0);
+ }
+
+ mc_search_free (search);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_files (WPanel * panel)
+{
+ panel_select_unselect_files (panel, _("Select"), MC_HISTORY_FM_PANEL_SELECT,
+ "[Select/Unselect Files]", TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_unselect_files (WPanel * panel)
+{
+ panel_select_unselect_files (panel, _("Unselect"), MC_HISTORY_FM_PANEL_UNSELECT,
+ "[Select/Unselect Files]", FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_invert_files (WPanel * panel)
+{
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ file_entry_t *file = &panel->dir.list[i];
+
+ if (!panels_options.reverse_files_only || !S_ISDIR (file->st.st_mode))
+ do_file_mark (panel, i, file->f.marked ? 0 : 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_do_set_filter (WPanel * panel)
+{
+ /* *INDENT-OFF* */
+ file_filter_t ff = { .value = NULL, .handler = NULL, .flags = panel->filter.flags };
+ /* *INDENT-ON* */
+
+ ff.handler =
+ panel_select_unselect_files_dialog (&ff.flags, _("Filter"), MC_HISTORY_FM_PANEL_FILTER,
+ "[Filter...]", &ff.value);
+
+ if (ff.handler == NULL || ff.handler == SELECT_ERROR)
+ return;
+
+ if (ff.handler == SELECT_RESET)
+ ff.handler = NULL;
+
+ panel_set_filter (panel, &ff);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Incremental search of a file name in the panel.
+ * @param panel instance of WPanel structure
+ * @param c_code key code
+ */
+
+static void
+do_search (WPanel * panel, int c_code)
+{
+ int curr;
+ int i;
+ gboolean wrapped = FALSE;
+ char *act;
+ mc_search_t *search;
+ char *reg_exp, *esc_str;
+ gboolean is_found = FALSE;
+
+ if (c_code == KEY_BACKSPACE)
+ {
+ if (panel->quick_search.buffer->len != 0)
+ {
+ act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
+ str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
+ g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
+ }
+ panel->quick_search.chpoint = 0;
+ }
+ else
+ {
+ if (c_code != 0 && (gsize) panel->quick_search.chpoint < sizeof (panel->quick_search.ch))
+ {
+ panel->quick_search.ch[panel->quick_search.chpoint] = c_code;
+ panel->quick_search.chpoint++;
+ }
+
+ if (panel->quick_search.chpoint > 0)
+ {
+ switch (str_is_valid_char (panel->quick_search.ch, panel->quick_search.chpoint))
+ {
+ case -2:
+ return;
+ case -1:
+ panel->quick_search.chpoint = 0;
+ return;
+ default:
+ g_string_append_len (panel->quick_search.buffer, panel->quick_search.ch,
+ panel->quick_search.chpoint);
+ panel->quick_search.chpoint = 0;
+ }
+ }
+ }
+
+ reg_exp = g_strdup_printf ("%s*", panel->quick_search.buffer->str);
+ esc_str = strutils_escape (reg_exp, -1, ",|\\{}[]", TRUE);
+ search = mc_search_new (esc_str, NULL);
+ search->search_type = MC_SEARCH_T_GLOB;
+ search->is_entire_line = TRUE;
+
+ switch (panels_options.qsearch_mode)
+ {
+ case QSEARCH_CASE_SENSITIVE:
+ search->is_case_sensitive = TRUE;
+ break;
+ case QSEARCH_CASE_INSENSITIVE:
+ search->is_case_sensitive = FALSE;
+ break;
+ default:
+ search->is_case_sensitive = panel->sort_info.case_sensitive;
+ break;
+ }
+
+ curr = panel->current;
+
+ for (i = panel->current; !wrapped || i != panel->current; i++)
+ {
+ if (i >= panel->dir.len)
+ {
+ i = 0;
+ if (wrapped)
+ break;
+ wrapped = TRUE;
+ }
+ if (mc_search_run
+ (search, panel->dir.list[i].fname->str, 0, panel->dir.list[i].fname->len, NULL))
+ {
+ curr = i;
+ is_found = TRUE;
+ break;
+ }
+ }
+ if (is_found)
+ {
+ unselect_item (panel);
+ panel->current = curr;
+ select_item (panel);
+ widget_draw (WIDGET (panel));
+ }
+ else if (c_code != KEY_BACKSPACE)
+ {
+ act = panel->quick_search.buffer->str + panel->quick_search.buffer->len;
+ str_prev_noncomb_char (&act, panel->quick_search.buffer->str);
+ g_string_set_size (panel->quick_search.buffer, act - panel->quick_search.buffer->str);
+ }
+ mc_search_free (search);
+ g_free (reg_exp);
+ g_free (esc_str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Start new search.
+ * @param panel instance of WPanel structure
+ */
+
+static void
+start_search (WPanel * panel)
+{
+ if (panel->quick_search.active)
+ {
+ if (panel->current == panel->dir.len - 1)
+ panel->current = 0;
+ else
+ move_down (panel);
+
+ /* in case if there was no search string we need to recall
+ previous string, with which we ended previous searching */
+ if (panel->quick_search.buffer->len == 0)
+ mc_g_string_copy (panel->quick_search.buffer, panel->quick_search.prev_buffer);
+
+ do_search (panel, 0);
+ }
+ else
+ {
+ panel->quick_search.active = TRUE;
+ g_string_set_size (panel->quick_search.buffer, 0);
+ panel->quick_search.ch[0] = '\0';
+ panel->quick_search.chpoint = 0;
+ display_mini_info (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+stop_search (WPanel * panel)
+{
+ if (!panel->quick_search.active)
+ return;
+
+ panel->quick_search.active = FALSE;
+
+ /* if user overrdied search string, we need to store it
+ to the quick_search.prev_buffer */
+ if (panel->quick_search.buffer->len != 0)
+ mc_g_string_copy (panel->quick_search.prev_buffer, panel->quick_search.buffer);
+
+ display_mini_info (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return TRUE if the Enter key has been processed, FALSE otherwise */
+
+static gboolean
+do_enter_on_file_entry (WPanel * panel, file_entry_t * fe)
+{
+ const char *fname = fe->fname->str;
+ vfs_path_t *full_name_vpath;
+ gboolean ok;
+
+ /*
+ * Directory or link to directory - change directory.
+ * Try the same for the entries on which mc_lstat() has failed.
+ */
+ if (S_ISDIR (fe->st.st_mode) || link_isdir (fe) || (fe->st.st_mode == 0))
+ {
+ vfs_path_t *fname_vpath;
+
+ fname_vpath = vfs_path_from_str (fname);
+ if (!panel_cd (panel, fname_vpath, cd_exact))
+ cd_error_message (fname);
+ vfs_path_free (fname_vpath, TRUE);
+ return TRUE;
+ }
+
+ full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
+
+ /* Try associated command */
+ ok = regex_command (full_name_vpath, "Open") != 0;
+ vfs_path_free (full_name_vpath, TRUE);
+ if (ok)
+ return TRUE;
+
+ /* Check if the file is executable */
+ full_name_vpath = vfs_path_append_new (panel->cwd_vpath, fname, (char *) NULL);
+ ok = (is_exe (fe->st.st_mode) && if_link_is_exe (full_name_vpath, fe));
+ vfs_path_free (full_name_vpath, TRUE);
+ if (!ok)
+ return FALSE;
+
+ if (confirm_execute
+ && query_dialog (_("The Midnight Commander"), _("Do you really want to execute?"), D_NORMAL,
+ 2, _("&Yes"), _("&No")) != 0)
+ return TRUE;
+
+ if (!vfs_current_is_local ())
+ {
+ int ret;
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vfs_get_raw_current_dir (), fname, (char *) NULL);
+ ret = mc_setctl (tmp_vpath, VFS_SETCTL_RUN, NULL);
+ vfs_path_free (tmp_vpath, TRUE);
+ /* We took action only if the dialog was shown or the execution was successful */
+ return confirm_execute || (ret == 0);
+ }
+
+ {
+ char *tmp, *cmd;
+
+ tmp = name_quote (fname, FALSE);
+ cmd = g_strconcat (".", PATH_SEP_STR, tmp, (char *) NULL);
+ g_free (tmp);
+ shell_execute (cmd, 0);
+ g_free (cmd);
+ }
+
+#ifdef HAVE_CHARSET
+ mc_global.source_codepage = default_source_codepage;
+#endif
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline gboolean
+do_enter (WPanel * panel)
+{
+ return do_enter_on_file_entry (panel, panel_current_entry (panel));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_cycle_listing_format (WPanel * panel)
+{
+ panel->list_format = (panel->list_format + 1) % LIST_FORMATS;
+
+ if (set_panel_formats (panel) == 0)
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chdir_other_panel (WPanel * panel)
+{
+ const file_entry_t *entry;
+ vfs_path_t *new_dir_vpath;
+ char *curr_entry = NULL;
+ WPanel *p;
+
+ entry = panel_current_entry (panel);
+
+ if (get_other_type () != view_listing)
+ create_panel (get_other_index (), view_listing);
+
+ if (S_ISDIR (entry->st.st_mode) || link_isdir (entry))
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, entry->fname->str, (char *) NULL);
+ else
+ {
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, "..", (char *) NULL);
+ curr_entry = strrchr (vfs_path_get_last_path_str (panel->cwd_vpath), PATH_SEP);
+ }
+
+ p = change_panel ();
+ panel_cd (p, new_dir_vpath, cd_exact);
+ vfs_path_free (new_dir_vpath, TRUE);
+
+ if (curr_entry != NULL)
+ panel_set_current_by_name (p, curr_entry);
+ (void) change_panel ();
+
+ move_down (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Make the current directory of the current panel also the current
+ * directory of the other panel. Put the other panel to the listing
+ * mode if needed. If the current panel is panelized, the other panel
+ * doesn't become panelized.
+ */
+
+static void
+panel_sync_other (const WPanel * panel)
+{
+ if (get_other_type () != view_listing)
+ create_panel (get_other_index (), view_listing);
+
+ panel_do_cd (other_panel, panel->cwd_vpath, cd_exact);
+
+ /* try to set current filename on the other panel */
+ if (!panel->is_panelized)
+ panel_set_current_by_name (other_panel, panel_current_entry (panel)->fname->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+chdir_to_readlink (WPanel * panel)
+{
+ const file_entry_t *fe;
+ vfs_path_t *new_dir_vpath;
+ char buffer[MC_MAXPATHLEN];
+ int i;
+ struct stat st;
+ vfs_path_t *panel_fname_vpath;
+ gboolean ok;
+ WPanel *cpanel;
+
+ if (get_other_type () != view_listing)
+ return;
+
+ fe = panel_current_entry (panel);
+
+ if (!S_ISLNK (fe->st.st_mode))
+ return;
+
+ i = readlink (fe->fname->str, buffer, MC_MAXPATHLEN - 1);
+ if (i < 0)
+ return;
+
+ panel_fname_vpath = vfs_path_from_str (fe->fname->str);
+ ok = (mc_stat (panel_fname_vpath, &st) >= 0);
+ vfs_path_free (panel_fname_vpath, TRUE);
+ if (!ok)
+ return;
+
+ buffer[i] = '\0';
+ if (!S_ISDIR (st.st_mode))
+ {
+ char *p;
+
+ p = strrchr (buffer, PATH_SEP);
+ if (p != NULL && p[1] == '\0')
+ {
+ *p = '\0';
+ p = strrchr (buffer, PATH_SEP);
+ }
+ if (p == NULL)
+ return;
+
+ p[1] = '\0';
+ }
+ if (IS_PATH_SEP (*buffer))
+ new_dir_vpath = vfs_path_from_str (buffer);
+ else
+ new_dir_vpath = vfs_path_append_new (panel->cwd_vpath, buffer, (char *) NULL);
+
+ cpanel = change_panel ();
+ panel_cd (cpanel, new_dir_vpath, cd_exact);
+ vfs_path_free (new_dir_vpath, TRUE);
+ (void) change_panel ();
+
+ move_down (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ function return 0 if not found and REAL_INDEX+1 if found
+ */
+
+static gsize
+panel_get_format_field_index_by_name (const WPanel * panel, const char *name)
+{
+ GSList *format;
+ gsize lc_index;
+
+ for (lc_index = 1, format = panel->format;
+ format != NULL && strcmp (((format_item_t *) format->data)->title, name) != 0;
+ format = g_slist_next (format), lc_index++)
+ ;
+
+ if (format == NULL)
+ lc_index = 0;
+
+ return lc_index;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const panel_field_t *
+panel_get_sortable_field_by_format (const WPanel * panel, gsize lc_index)
+{
+ const panel_field_t *pfield;
+ const format_item_t *format;
+
+ format = (const format_item_t *) g_slist_nth_data (panel->format, lc_index);
+ if (format == NULL)
+ return NULL;
+
+ pfield = panel_get_field_by_title (format->title);
+ if (pfield == NULL)
+ return NULL;
+ if (pfield->sort_routine == NULL)
+ return NULL;
+ return pfield;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_toggle_sort_order_prev (WPanel * panel)
+{
+ gsize lc_index, i;
+ const char *title;
+ const panel_field_t *pfield = NULL;
+
+ title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
+ lc_index = panel_get_format_field_index_by_name (panel, title);
+
+ if (lc_index > 1)
+ {
+ /* search for prev sortable column in panel format */
+ for (i = lc_index - 1;
+ i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
+ ;
+ }
+
+ if (pfield == NULL)
+ {
+ /* Sortable field not found. Try to search in each array */
+ for (i = g_slist_length (panel->format);
+ i != 0 && (pfield = panel_get_sortable_field_by_format (panel, i - 1)) == NULL; i--)
+ ;
+ }
+
+ if (pfield != NULL)
+ {
+ panel->sort_field = pfield;
+ panel_set_sort_order (panel, pfield);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_toggle_sort_order_next (WPanel * panel)
+{
+ gsize lc_index, i;
+ const panel_field_t *pfield = NULL;
+ gsize format_field_count;
+ const char *title;
+
+ format_field_count = g_slist_length (panel->format);
+ title = panel_get_title_without_hotkey (panel->sort_field->title_hotkey);
+ lc_index = panel_get_format_field_index_by_name (panel, title);
+
+ if (lc_index != 0 && lc_index != format_field_count)
+ {
+ /* search for prev sortable column in panel format */
+ for (i = lc_index;
+ i != format_field_count
+ && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
+ ;
+ }
+
+ if (pfield == NULL)
+ {
+ /* Sortable field not found. Try to search in each array */
+ for (i = 0;
+ i != format_field_count
+ && (pfield = panel_get_sortable_field_by_format (panel, i)) == NULL; i++)
+ ;
+ }
+
+ if (pfield != NULL)
+ {
+ panel->sort_field = pfield;
+ panel_set_sort_order (panel, pfield);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_select_sort_order (WPanel * panel)
+{
+ const panel_field_t *sort_order;
+
+ sort_order = sort_box (&panel->sort_info, panel->sort_field);
+ if (sort_order != NULL)
+ {
+ panel->sort_field = sort_order;
+ panel_set_sort_order (panel, sort_order);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * panel_content_scroll_left:
+ * @param panel the pointer to the panel on which we operate
+ *
+ * scroll long filename to the left (decrement scroll pointer)
+ *
+ */
+
+static void
+panel_content_scroll_left (WPanel * panel)
+{
+ if (panel->content_shift > -1)
+ {
+ if (panel->content_shift > panel->max_shift)
+ panel->content_shift = panel->max_shift;
+
+ panel->content_shift--;
+ show_dir (panel);
+ paint_dir (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * panel_content_scroll_right:
+ * @param panel the pointer to the panel on which we operate
+ *
+ * scroll long filename to the right (increment scroll pointer)
+ *
+ */
+
+static void
+panel_content_scroll_right (WPanel * panel)
+{
+ if (panel->content_shift < 0 || panel->content_shift < panel->max_shift)
+ {
+ panel->content_shift++;
+ show_dir (panel);
+ paint_dir (panel);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_set_sort_type_by_id (WPanel * panel, const char *name)
+{
+ if (strcmp (panel->sort_field->id, name) == 0)
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ else
+ {
+ const panel_field_t *sort_order;
+
+ sort_order = panel_get_field_by_id (name);
+ if (sort_order == NULL)
+ return;
+
+ panel->sort_field = sort_order;
+ }
+
+ panel_set_sort_order (panel, panel->sort_field);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * If we moved to the parent directory move the 'current' pointer to
+ * the old directory name; If we leave VFS dir, remove FS specificator.
+ *
+ * You do _NOT_ want to add any vfs aware code here. <pavel@ucw.cz>
+ */
+
+static const char *
+get_parent_dir_name (const vfs_path_t * cwd_vpath, const vfs_path_t * lwd_vpath)
+{
+ size_t llen, clen;
+ const char *p, *lwd;
+
+ llen = vfs_path_len (lwd_vpath);
+ clen = vfs_path_len (cwd_vpath);
+
+ if (llen <= clen)
+ return NULL;
+
+ lwd = vfs_path_as_str (lwd_vpath);
+
+ p = g_strrstr (lwd, VFS_PATH_URL_DELIMITER);
+
+ if (p == NULL)
+ {
+ const char *cwd;
+
+ cwd = vfs_path_as_str (cwd_vpath);
+
+ p = strrchr (lwd, PATH_SEP);
+
+ if (p != NULL && strncmp (cwd, lwd, (size_t) (p - lwd)) == 0
+ && (clen == (size_t) (p - lwd) || (p == lwd && IS_PATH_SEP (cwd[0]) && cwd[1] == '\0')))
+ return (p + 1);
+
+ return NULL;
+ }
+
+ /* skip VFS prefix */
+ while (--p > lwd && !IS_PATH_SEP (*p))
+ ;
+ /* get last component */
+ while (--p > lwd && !IS_PATH_SEP (*p))
+ ;
+
+ /* return last component */
+ return (p != lwd || IS_PATH_SEP (*p)) ? p + 1 : p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Wrapper for do_subshell_chdir, check for availability of subshell */
+
+static void
+subshell_chdir (const vfs_path_t * vpath)
+{
+#ifdef ENABLE_SUBSHELL
+ if (mc_global.tty.use_subshell && vfs_current_is_local ())
+ do_subshell_chdir (vpath, FALSE);
+#else /* ENABLE_SUBSHELL */
+ (void) vpath;
+#endif /* ENABLE_SUBSHELL */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the current directory of the panel.
+ * Don't record change in the directory history.
+ */
+
+static gboolean
+panel_do_cd_int (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type)
+{
+ vfs_path_t *olddir_vpath;
+
+ /* Convert *new_path to a suitable pathname, handle ~user */
+ if (cd_type == cd_parse_command)
+ {
+ const vfs_path_element_t *element;
+
+ element = vfs_path_get_by_index (new_dir_vpath, 0);
+ if (strcmp (element->path, "-") == 0)
+ new_dir_vpath = panel->lwd_vpath;
+ }
+
+ if (mc_chdir (new_dir_vpath) == -1)
+ return FALSE;
+
+ /* Success: save previous directory, shutdown status of previous dir */
+ olddir_vpath = vfs_path_clone (panel->cwd_vpath);
+ panel_set_lwd (panel, panel->cwd_vpath);
+ input_complete_free (cmdline);
+
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ vfs_setup_cwd ();
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+
+ vfs_release_path (olddir_vpath);
+
+ subshell_chdir (panel->cwd_vpath);
+
+ /* Reload current panel */
+ panel_clean_dir (panel);
+
+ if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ panel_set_current_by_name (panel, get_parent_dir_name (panel->cwd_vpath, olddir_vpath));
+
+ load_hint (FALSE);
+ panel->dirty = TRUE;
+ update_xterm_title_path ();
+
+ vfs_path_free (olddir_vpath, TRUE);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_next (WPanel * panel)
+{
+ gboolean ok;
+
+ do
+ {
+ GList *next;
+
+ ok = TRUE;
+ next = g_list_next (panel->dir_history.current);
+ if (next != NULL)
+ {
+ vfs_path_t *data_vpath;
+
+ data_vpath = vfs_path_from_str ((char *) next->data);
+ ok = panel_do_cd_int (panel, data_vpath, cd_exact);
+ vfs_path_free (data_vpath, TRUE);
+ panel->dir_history.current = next;
+ }
+ /* skip directories that present in history but absent in file system */
+ }
+ while (!ok);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_prev (WPanel * panel)
+{
+ gboolean ok;
+
+ do
+ {
+ GList *prev;
+
+ ok = TRUE;
+ prev = g_list_previous (panel->dir_history.current);
+ if (prev != NULL)
+ {
+ vfs_path_t *data_vpath;
+
+ data_vpath = vfs_path_from_str ((char *) prev->data);
+ ok = panel_do_cd_int (panel, data_vpath, cd_exact);
+ vfs_path_free (data_vpath, TRUE);
+ panel->dir_history.current = prev;
+ }
+ /* skip directories that present in history but absent in file system */
+ }
+ while (!ok);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+directory_history_list (WPanel * panel)
+{
+ history_descriptor_t hd;
+ gboolean ok = FALSE;
+ size_t pos;
+
+ pos = g_list_position (panel->dir_history.current, panel->dir_history.list);
+
+ history_descriptor_init (&hd, WIDGET (panel)->rect.y, WIDGET (panel)->rect.x,
+ panel->dir_history.list, (int) pos);
+ history_show (&hd);
+
+ panel->dir_history.list = hd.list;
+ if (hd.text != NULL)
+ {
+ vfs_path_t *s_vpath;
+
+ s_vpath = vfs_path_from_str (hd.text);
+ ok = panel_do_cd_int (panel, s_vpath, cd_exact);
+ if (ok)
+ directory_history_add (panel, panel->cwd_vpath);
+ else
+ cd_error_message (hd.text);
+ vfs_path_free (s_vpath, TRUE);
+ g_free (hd.text);
+ }
+
+ if (!ok)
+ {
+ /* Since history is fully modified in history_show(), panel->dir_history actually
+ * points to the invalid place. Try restore current position here. */
+
+ size_t i;
+
+ panel->dir_history.current = panel->dir_history.list;
+
+ for (i = 0; i <= pos; i++)
+ {
+ GList *prev;
+
+ prev = g_list_previous (panel->dir_history.current);
+ if (prev == NULL)
+ break;
+
+ panel->dir_history.current = prev;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_execute_cmd (WPanel * panel, long command)
+{
+ int res = MSG_HANDLED;
+
+ if (command != CK_Search)
+ stop_search (panel);
+
+ switch (command)
+ {
+ case CK_Up:
+ case CK_Down:
+ case CK_Left:
+ case CK_Right:
+ case CK_Bottom:
+ case CK_Top:
+ case CK_PageDown:
+ case CK_PageUp:
+ /* reset state of marks flag */
+ state_mark = -1;
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_CycleListingFormat:
+ panel_cycle_listing_format (panel);
+ break;
+ case CK_PanelOtherCd:
+ chdir_other_panel (panel);
+ break;
+ case CK_PanelOtherCdLink:
+ chdir_to_readlink (panel);
+ break;
+ case CK_CopySingle:
+ copy_cmd_local (panel);
+ break;
+ case CK_DeleteSingle:
+ delete_cmd_local (panel);
+ break;
+ case CK_Enter:
+ do_enter (panel);
+ break;
+ case CK_ViewRaw:
+ view_raw_cmd (panel);
+ break;
+ case CK_EditNew:
+ edit_cmd_new ();
+ break;
+ case CK_MoveSingle:
+ rename_cmd_local (panel);
+ break;
+ case CK_SelectInvert:
+ panel_select_invert_files (panel);
+ break;
+ case CK_Select:
+ panel_select_files (panel);
+ break;
+ case CK_SelectExt:
+ panel_select_ext_cmd (panel);
+ break;
+ case CK_Unselect:
+ panel_unselect_files (panel);
+ break;
+ case CK_Filter:
+ panel_do_set_filter (panel);
+ break;
+ case CK_PageDown:
+ next_page (panel);
+ break;
+ case CK_PageUp:
+ prev_page (panel);
+ break;
+ case CK_CdChild:
+ goto_child_dir (panel);
+ break;
+ case CK_CdParent:
+ goto_parent_dir (panel);
+ break;
+ case CK_History:
+ directory_history_list (panel);
+ break;
+ case CK_HistoryNext:
+ directory_history_next (panel);
+ break;
+ case CK_HistoryPrev:
+ directory_history_prev (panel);
+ break;
+ case CK_BottomOnScreen:
+ goto_bottom_file (panel);
+ break;
+ case CK_MiddleOnScreen:
+ goto_middle_file (panel);
+ break;
+ case CK_TopOnScreen:
+ goto_top_file (panel);
+ break;
+ case CK_Mark:
+ mark_file (panel);
+ break;
+ case CK_MarkUp:
+ mark_file_up (panel);
+ break;
+ case CK_MarkDown:
+ mark_file_down (panel);
+ break;
+ case CK_MarkLeft:
+ mark_file_left (panel);
+ break;
+ case CK_MarkRight:
+ mark_file_right (panel);
+ break;
+ case CK_CdParentSmart:
+ res = force_maybe_cd (panel);
+ break;
+ case CK_Up:
+ move_up (panel);
+ break;
+ case CK_Down:
+ move_down (panel);
+ break;
+ case CK_Left:
+ res = move_left (panel);
+ break;
+ case CK_Right:
+ res = move_right (panel);
+ break;
+ case CK_Bottom:
+ move_end (panel);
+ break;
+ case CK_Top:
+ move_home (panel);
+ break;
+#ifdef HAVE_CHARSET
+ case CK_SelectCodepage:
+ panel_change_encoding (panel);
+ break;
+#endif
+ case CK_ScrollLeft:
+ panel_content_scroll_left (panel);
+ break;
+ case CK_ScrollRight:
+ panel_content_scroll_right (panel);
+ break;
+ case CK_Search:
+ start_search (panel);
+ break;
+ case CK_SearchStop:
+ break;
+ case CK_PanelOtherSync:
+ panel_sync_other (panel);
+ break;
+ case CK_Sort:
+ panel_select_sort_order (panel);
+ break;
+ case CK_SortPrev:
+ panel_toggle_sort_order_prev (panel);
+ break;
+ case CK_SortNext:
+ panel_toggle_sort_order_next (panel);
+ break;
+ case CK_SortReverse:
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ panel_set_sort_order (panel, panel->sort_field);
+ break;
+ case CK_SortByName:
+ panel_set_sort_type_by_id (panel, "name");
+ break;
+ case CK_SortByExt:
+ panel_set_sort_type_by_id (panel, "extension");
+ break;
+ case CK_SortBySize:
+ panel_set_sort_type_by_id (panel, "size");
+ break;
+ case CK_SortByMTime:
+ panel_set_sort_type_by_id (panel, "mtime");
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_key (WPanel * panel, int key)
+{
+ long command;
+
+ if (is_abort_char (key))
+ {
+ stop_search (panel);
+ return MSG_HANDLED;
+ }
+
+ if (panel->quick_search.active && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ do_search (panel, key);
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (panel), key);
+ if (command != CK_IgnoreKey)
+ return panel_execute_cmd (panel, command);
+
+ if (panels_options.torben_fj_mode && key == ALT ('h'))
+ {
+ goto_middle_file (panel);
+ return MSG_HANDLED;
+ }
+
+ if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ start_search (panel);
+ do_search (panel, key);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panel_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WPanel *panel = PANEL (w);
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *bb;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* subscribe to "history_load" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w, NULL);
+ /* subscribe to "history_save" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w, NULL);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ /* Repaint everything, including frame and separator */
+ widget_erase (w);
+ show_dir (panel);
+ panel_print_header (panel);
+ adjust_top_file (panel);
+ paint_dir (panel);
+ mini_info_separator (panel);
+ display_mini_info (panel);
+ panel->dirty = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_FOCUS:
+ state_mark = -1;
+ current_panel = panel;
+ panel->active = TRUE;
+
+ if (mc_chdir (panel->cwd_vpath) != 0)
+ {
+ char *cwd;
+
+ cwd = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
+ cd_error_message (cwd);
+ g_free (cwd);
+ }
+ else
+ subshell_chdir (panel->cwd_vpath);
+
+ update_xterm_title_path ();
+ select_item (panel);
+
+ bb = buttonbar_find (h);
+ midnight_set_buttonbar (bb);
+ widget_draw (WIDGET (bb));
+ return MSG_HANDLED;
+
+ case MSG_UNFOCUS:
+ /* Janne: look at this for the multiple panel options */
+ stop_search (panel);
+ panel->active = FALSE;
+ unselect_item (panel);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return panel_key (panel, parm);
+
+ case MSG_ACTION:
+ return panel_execute_cmd (panel, parm);
+
+ case MSG_DESTROY:
+ vfs_stamp_path (panel->cwd_vpath);
+ /* unsubscribe from "history_load" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, panel_load_history, w);
+ /* unsubscribe from "history_save" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, panel_save_history, w);
+ panel_destroy (panel);
+ free_my_statfs ();
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* */
+/* Panel mouse events support routines */
+/* */
+
+static void
+mouse_toggle_mark (WPanel * panel)
+{
+ do_mark_file (panel, MARK_DONT_MOVE);
+ mouse_marking = (panel_current_entry (panel)->f.marked != 0);
+ mouse_mark_panel = current_panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mouse_set_mark (WPanel * panel)
+{
+ if (mouse_mark_panel == panel)
+ {
+ const file_entry_t *fe;
+
+ fe = panel_current_entry (panel);
+
+ if (mouse_marking && fe->f.marked == 0)
+ do_mark_file (panel, MARK_DONT_MOVE);
+ else if (!mouse_marking && fe->f.marked != 0)
+ do_mark_file (panel, MARK_DONT_MOVE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+mark_if_marking (WPanel * panel, const mouse_event_t * event, int previous_current)
+{
+ if ((event->buttons & GPM_B_RIGHT) == 0)
+ return;
+
+ if (event->msg == MSG_MOUSE_DOWN)
+ mouse_toggle_mark (panel);
+ else
+ {
+ int pcurr, curr1, curr2;
+
+ pcurr = panel->current;
+ curr1 = MIN (previous_current, panel->current);
+ curr2 = MAX (previous_current, panel->current);
+
+ for (; curr1 <= curr2; curr1++)
+ {
+ panel->current = curr1;
+ mouse_set_mark (panel);
+ }
+
+ panel->current = pcurr;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Determine which column was clicked, and sort the panel on
+ * that column, or reverse sort on that column if already
+ * sorted on that column.
+ */
+
+static void
+mouse_sort_col (WPanel * panel, int x)
+{
+ int i = 0;
+ GSList *format;
+ const char *lc_sort_name = NULL;
+ panel_field_t *col_sort_format = NULL;
+
+ for (format = panel->format; format != NULL; format = g_slist_next (format))
+ {
+ format_item_t *fi = (format_item_t *) format->data;
+
+ i += fi->field_len;
+ if (x < i + 1)
+ {
+ /* found column */
+ lc_sort_name = fi->title;
+ break;
+ }
+ }
+
+ if (lc_sort_name == NULL)
+ return;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ {
+ const char *title;
+
+ title = panel_get_title_without_hotkey (panel_fields[i].title_hotkey);
+ if (panel_fields[i].sort_routine != NULL && strcmp (title, lc_sort_name) == 0)
+ {
+ col_sort_format = &panel_fields[i];
+ break;
+ }
+ }
+
+ if (col_sort_format != NULL)
+ {
+ if (panel->sort_field == col_sort_format)
+ /* reverse the sort if clicked column is already the sorted column */
+ panel->sort_info.reverse = !panel->sort_info.reverse;
+ else
+ /* new sort is forced to be ascending */
+ panel->sort_info.reverse = FALSE;
+
+ panel_set_sort_order (panel, col_sort_format);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panel_mouse_is_on_item (const WPanel * panel, int y, int x)
+{
+ int lines, col_width, col;
+
+ if (y < 0)
+ return MOUSE_UPPER_FILE_LIST;
+
+ lines = panel_lines (panel);
+ if (y >= lines)
+ return MOUSE_BELOW_FILE_LIST;
+
+ col_width = (CONST_WIDGET (panel)->rect.cols - 2) / panel->list_cols;
+ /* column where mouse is */
+ col = x / col_width;
+
+ y += panel->top + lines * col;
+
+ /* are we below or in the next column of last file? */
+ if (y > panel->dir.len)
+ return MOUSE_AFTER_LAST_FILE;
+
+ /* we are on item of the file file; return an index to select a file */
+ return y;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WPanel *panel = PANEL (w);
+ gboolean is_active;
+
+ is_active = widget_is_active (w);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ if (event->y == 0)
+ {
+ /* top frame */
+ if (event->x == 1)
+ /* "<" button */
+ directory_history_prev (panel);
+ else if (event->x == w->rect.cols - 2)
+ /* ">" button */
+ directory_history_next (panel);
+ else if (event->x >= w->rect.cols - 5 && event->x <= w->rect.cols - 3)
+ /* "^" button */
+ directory_history_list (panel);
+ else if (event->x == w->rect.cols - 6)
+ /* "." button show/hide hidden files */
+ send_message (filemanager, NULL, MSG_ACTION, CK_ShowHidden, NULL);
+ else
+ {
+ /* no other events on 1st line, return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ /* avoid extra panel redraw */
+ panel->dirty = FALSE;
+ }
+ break;
+ }
+
+ if (event->y == 1)
+ {
+ /* sort on clicked column */
+ mouse_sort_col (panel, event->x + 1);
+ break;
+ }
+
+ if (!is_active)
+ (void) change_panel ();
+ MC_FALLTHROUGH;
+
+ case MSG_MOUSE_DRAG:
+ {
+ int my_index;
+ int previous_current;
+
+ my_index = panel_mouse_is_on_item (panel, event->y - 2, event->x);
+ previous_current = panel->current;
+
+ switch (my_index)
+ {
+ case MOUSE_UPPER_FILE_LIST:
+ move_up (panel);
+ mark_if_marking (panel, event, previous_current);
+ break;
+
+ case MOUSE_BELOW_FILE_LIST:
+ move_down (panel);
+ mark_if_marking (panel, event, previous_current);
+ break;
+
+ case MOUSE_AFTER_LAST_FILE:
+ break; /* do nothing */
+
+ default:
+ if (my_index != panel->current)
+ {
+ unselect_item (panel);
+ panel->current = my_index;
+ select_item (panel);
+ }
+
+ mark_if_marking (panel, event, previous_current);
+ break;
+ }
+ }
+ break;
+
+ case MSG_MOUSE_UP:
+ break;
+
+ case MSG_MOUSE_CLICK:
+ if ((event->count & GPM_DOUBLE) != 0 && (event->buttons & GPM_B_LEFT) != 0 &&
+ panel_mouse_is_on_item (panel, event->y - 2, event->x) >= 0)
+ do_enter (panel);
+ break;
+
+ case MSG_MOUSE_MOVE:
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ if (is_active)
+ {
+ if (panels_options.mouse_move_pages && panel->top > 0)
+ prev_page (panel);
+ else /* We are in first page */
+ move_up (panel);
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ if (is_active)
+ {
+ if (panels_options.mouse_move_pages
+ && panel->top + panel_items (panel) < panel->dir.len)
+ next_page (panel);
+ else /* We are in last page */
+ move_down (panel);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (panel->dirty)
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+reload_panelized (WPanel * panel)
+{
+ int i, j;
+ dir_list *list = &panel->dir;
+
+ /* refresh current VFS directory required for vfs_path_from_str() */
+ (void) mc_chdir (panel->cwd_vpath);
+
+ for (i = 0, j = 0; i < list->len; i++)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (list->list[i].fname->str);
+ if (mc_lstat (vpath, &list->list[i].st) != 0)
+ g_string_free (list->list[i].fname, TRUE);
+ else
+ {
+ if (j != i)
+ list->list[j] = list->list[i];
+ j++;
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+ if (j == 0)
+ dir_list_init (list);
+ else
+ list->len = j;
+
+ recalculate_panel_summary (panel);
+
+ if (panel != current_panel)
+ (void) mc_chdir (current_panel->cwd_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_one_panel_widget (WPanel * panel, panel_update_flags_t flags, const char *current_file)
+{
+ gboolean free_pointer;
+ char *my_current_file = NULL;
+
+ if ((flags & UP_RELOAD) != 0)
+ {
+ panel->is_panelized = FALSE;
+ mc_setctl (panel->cwd_vpath, VFS_SETCTL_FLUSH, NULL);
+ memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
+ }
+
+ /* If current_file == -1 (an invalid pointer) then preserve current */
+ free_pointer = current_file == UP_KEEPSEL;
+
+ if (free_pointer)
+ {
+ const GString *fname;
+
+ fname = panel_current_entry (panel)->fname;
+ my_current_file = g_strndup (fname->str, fname->len);
+ current_file = my_current_file;
+ }
+
+ if (panel->is_panelized)
+ reload_panelized (panel);
+ else
+ panel_reload (panel);
+
+ panel_set_current_by_name (panel, current_file);
+ panel->dirty = TRUE;
+
+ if (free_pointer)
+ g_free (my_current_file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_one_panel (int which, panel_update_flags_t flags, const char *current_file)
+{
+ if (get_panel_type (which) == view_listing)
+ {
+ WPanel *panel;
+
+ panel = PANEL (get_panel_widget (which));
+ if (panel->is_panelized)
+ flags &= ~UP_RELOAD;
+ update_one_panel_widget (panel, flags, current_file);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_set_current (WPanel * panel, int i)
+{
+ if (i != panel->current)
+ {
+ panel->dirty = TRUE;
+ panel->current = i;
+ panel->top = panel->current - (WIDGET (panel)->rect.lines - 2) / 2;
+ if (panel->top < 0)
+ panel->top = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+event_update_panels (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ update_panels (UP_RELOAD, UP_KEEPSEL);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* event callback */
+static gboolean
+panel_save_current_file_to_clip_file (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ (void) event_group_name;
+ (void) event_name;
+ (void) init_data;
+ (void) data;
+
+ if (current_panel->marked == 0)
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file",
+ (gpointer) panel_current_entry (current_panel)->fname->str);
+ else
+ {
+ int i;
+ gboolean first = TRUE;
+ char *flist = NULL;
+
+ for (i = 0; i < current_panel->dir.len; i++)
+ {
+ const file_entry_t *fe = &current_panel->dir.list[i];
+
+ if (fe->f.marked != 0)
+ { /* Skip the unmarked ones */
+ if (first)
+ {
+ flist = g_strndup (fe->fname->str, fe->fname->len);
+ first = FALSE;
+ }
+ else
+ {
+ /* Add empty lines after the file */
+ char *tmp;
+
+ tmp = g_strconcat (flist, "\n", fe->fname->str, (char *) NULL);
+ g_free (flist);
+ flist = tmp;
+ }
+ }
+ }
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", (gpointer) flist);
+ g_free (flist);
+ }
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+panel_recursive_cd_to_parent (const vfs_path_t * vpath)
+{
+ vfs_path_t *cwd_vpath;
+
+ cwd_vpath = vfs_path_clone (vpath);
+
+ while (mc_chdir (cwd_vpath) < 0)
+ {
+ const char *panel_cwd_path;
+ vfs_path_t *tmp_vpath;
+
+ /* check if path contains only '/' */
+ panel_cwd_path = vfs_path_as_str (cwd_vpath);
+ if (panel_cwd_path != NULL && IS_PATH_SEP (panel_cwd_path[0]) && panel_cwd_path[1] == '\0')
+ {
+ vfs_path_free (cwd_vpath, TRUE);
+ return NULL;
+ }
+
+ tmp_vpath = vfs_path_vtokens_get (cwd_vpath, 0, -1);
+ vfs_path_free (cwd_vpath, TRUE);
+ cwd_vpath =
+ vfs_path_build_filename (PATH_SEP_STR, vfs_path_as_str (tmp_vpath), (char *) NULL);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ return cwd_vpath;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panel_dir_list_callback (dir_list_cb_state_t state, void *data)
+{
+ static int count = 0;
+
+ (void) data;
+
+ switch (state)
+ {
+ case DIR_OPEN:
+ count = 0;
+ break;
+
+ case DIR_READ:
+ count++;
+ if ((count & 15) == 0)
+ rotate_dash (TRUE);
+ break;
+
+ case DIR_CLOSE:
+ rotate_dash (FALSE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_current_by_name (WPanel * panel, const char *name)
+{
+ int i;
+ char *subdir;
+
+ if (name == NULL)
+ {
+ panel_set_current (panel, 0);
+ return;
+ }
+
+ /* We only want the last component of the directory,
+ * and from this only the name without suffix.
+ * Cut prefix if the panel is not panelized */
+ if (panel->is_panelized)
+ subdir = vfs_strip_suffix_from_filename (name);
+ else
+ subdir = vfs_strip_suffix_from_filename (x_basename (name));
+
+ /* Search that subdir or filename without prefix (if not panelized panel),
+ make it current if found */
+ for (i = 0; i < panel->dir.len; i++)
+ if (strcmp (subdir, panel->dir.list[i].fname->str) == 0)
+ {
+ panel_set_current (panel, i);
+ g_free (subdir);
+ return;
+ }
+
+ /* Make current near the filee that is missing */
+ if (panel->current >= panel->dir.len)
+ panel_set_current (panel, panel->dir.len - 1);
+ g_free (subdir);
+
+ select_item (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_clean_dir (WPanel * panel)
+{
+ panel->top = 0;
+ panel->current = 0;
+ panel->marked = 0;
+ panel->dirs_marked = 0;
+ panel->total = 0;
+ panel->quick_search.active = FALSE;
+ panel->is_panelized = FALSE;
+ panel->dirty = TRUE;
+ panel->content_shift = -1;
+ panel->max_shift = -1;
+
+ dir_list_free_list (&panel->dir);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set Up panel's current dir object
+ *
+ * @param panel panel object
+ * @param path_str string contain path
+ */
+
+void
+panel_set_cwd (WPanel * panel, const vfs_path_t * vpath)
+{
+ if (vpath != panel->cwd_vpath) /* check if new vpath is not the panel->cwd_vpath object */
+ {
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ panel->cwd_vpath = vfs_path_clone (vpath);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set Up panel's last working dir object
+ *
+ * @param panel panel object
+ * @param path_str string contain path
+ */
+
+void
+panel_set_lwd (WPanel * panel, const vfs_path_t * vpath)
+{
+ if (vpath != panel->lwd_vpath) /* check if new vpath is not the panel->lwd_vpath object */
+ {
+ vfs_path_free (panel->lwd_vpath, TRUE);
+ panel->lwd_vpath = vfs_path_clone (vpath);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Creatie an empty panel with specified size.
+ *
+ * @param panel_name name of panel for setup receiving
+ *
+ * @return new instance of WPanel
+ */
+
+WPanel *
+panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+ WPanel *panel;
+ Widget *w;
+ char *section;
+ int i, err;
+
+ panel = g_new0 (WPanel, 1);
+ w = WIDGET (panel);
+ widget_init (w, &r, panel_callback, panel_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = panel_map;
+
+ panel->dir.size = DIR_LIST_MIN_SIZE;
+ panel->dir.list = g_new (file_entry_t, panel->dir.size);
+ panel->dir.len = 0;
+ panel->dir.callback = panel_dir_list_callback;
+
+ panel->list_cols = 1;
+ panel->brief_cols = 2;
+ panel->dirty = TRUE;
+ panel->content_shift = -1;
+ panel->max_shift = -1;
+
+ panel->list_format = list_full;
+ panel->user_format = g_strdup (DEFAULT_USER_FORMAT);
+
+ panel->filter.flags = FILE_FILTER_DEFAULT_FLAGS;
+
+ for (i = 0; i < LIST_FORMATS; i++)
+ panel->user_status_format[i] = g_strdup (DEFAULT_USER_FORMAT);
+
+#ifdef HAVE_CHARSET
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+#endif
+
+ panel->frame_size = frame_half;
+
+ panel->quick_search.buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+ panel->quick_search.prev_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ panel->name = g_strdup (panel_name);
+ panel->dir_history.name = g_strconcat ("Dir Hist ", panel->name, (char *) NULL);
+ /* directories history will be get later */
+
+ section = g_strconcat ("Temporal:", panel->name, (char *) NULL);
+ if (!mc_config_has_group (mc_global.main_config, section))
+ {
+ g_free (section);
+ section = g_strdup (panel->name);
+ }
+ panel_load_setup (panel, section);
+ g_free (section);
+
+ if (panel->filter.value != NULL)
+ {
+ gboolean case_sens = (panel->filter.flags & SELECT_MATCH_CASE) != 0;
+ gboolean shell_patterns = (panel->filter.flags & SELECT_SHELL_PATTERNS) != 0;
+
+ panel->filter.handler = mc_search_new (panel->filter.value, NULL);
+ panel->filter.handler->search_type = shell_patterns ? MC_SEARCH_T_GLOB : MC_SEARCH_T_REGEX;
+ panel->filter.handler->is_entire_line = TRUE;
+ panel->filter.handler->is_case_sensitive = case_sens;
+
+ /* FIXME: silent check -- do not display an error message */
+ if (!mc_search_prepare (panel->filter.handler))
+ file_filter_clear (&panel->filter);
+ }
+
+ /* Load format strings */
+ err = set_panel_formats (panel);
+ if (err != 0)
+ set_panel_formats (panel);
+
+ return panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation for specified size and directory.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines vertical size
+ * @param cols horizontal size
+ * @param vpath working panel directory. If NULL then current directory is used
+ *
+ * @return new instance of WPanel
+ */
+
+WPanel *
+panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols,
+ const vfs_path_t * vpath)
+{
+ WPanel *panel;
+ char *curdir = NULL;
+#ifdef HAVE_CHARSET
+ const vfs_path_element_t *path_element;
+#endif
+
+ panel = panel_sized_empty_new (panel_name, y, x, lines, cols);
+
+ if (vpath != NULL)
+ {
+ curdir = vfs_get_cwd ();
+ panel_set_cwd (panel, vpath);
+ }
+ else
+ {
+ vfs_setup_cwd ();
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ }
+
+ panel_set_lwd (panel, vfs_get_raw_current_dir ());
+
+#ifdef HAVE_CHARSET
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ panel->codepage = get_codepage_index (path_element->encoding);
+#endif
+
+ if (mc_chdir (panel->cwd_vpath) != 0)
+ {
+#ifdef HAVE_CHARSET
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+#endif
+ vfs_setup_cwd ();
+ vfs_path_free (panel->cwd_vpath, TRUE);
+ panel->cwd_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ }
+
+ /* Load the default format */
+ if (!dir_list_load (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ /* Restore old right path */
+ if (curdir != NULL)
+ {
+ vfs_path_t *tmp_vpath;
+ int err;
+
+ tmp_vpath = vfs_path_from_str (curdir);
+ mc_chdir (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ (void) err;
+ }
+ g_free (curdir);
+
+ return panel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_reload (WPanel * panel)
+{
+ struct stat current_stat;
+ vfs_path_t *cwd_vpath;
+
+ if (panels_options.fast_reload && stat (vfs_path_as_str (panel->cwd_vpath), &current_stat) == 0
+ && current_stat.st_ctime == panel->dir_stat.st_ctime
+ && current_stat.st_mtime == panel->dir_stat.st_mtime)
+ return;
+
+ cwd_vpath = panel_recursive_cd_to_parent (panel->cwd_vpath);
+ vfs_path_free (panel->cwd_vpath, TRUE);
+
+ if (cwd_vpath == NULL)
+ {
+ panel->cwd_vpath = vfs_path_from_str (PATH_SEP_STR);
+ panel_clean_dir (panel);
+ dir_list_init (&panel->dir);
+ return;
+ }
+
+ panel->cwd_vpath = cwd_vpath;
+ memset (&(panel->dir_stat), 0, sizeof (panel->dir_stat));
+ show_dir (panel);
+
+ if (!dir_list_reload (&panel->dir, panel->cwd_vpath, panel->sort_field->sort_routine,
+ &panel->sort_info, &panel->filter))
+ message (D_ERROR, MSG_ERROR, _("Cannot read directory contents"));
+
+ panel->dirty = TRUE;
+ if (panel->current >= panel->dir.len)
+ panel_set_current (panel, panel->dir.len - 1);
+
+ recalculate_panel_summary (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Switches the panel to the mode specified in the format */
+/* Setting up both format and status string. Return: 0 - on success; */
+/* 1 - format error; 2 - status error; 3 - errors in both formats. */
+
+int
+set_panel_formats (WPanel * p)
+{
+ GSList *form;
+ char *err = NULL;
+ int retcode = 0;
+
+ form = use_display_format (p, panel_format (p), &err, FALSE);
+
+ if (err != NULL)
+ {
+ g_free (err);
+ retcode = 1;
+ }
+ else
+ {
+ g_slist_free_full (p->format, (GDestroyNotify) format_item_free);
+ p->format = form;
+ }
+
+ if (panels_options.show_mini_info)
+ {
+ form = use_display_format (p, mini_status_format (p), &err, TRUE);
+
+ if (err != NULL)
+ {
+ g_free (err);
+ retcode += 2;
+ }
+ else
+ {
+ g_slist_free_full (p->status_format, (GDestroyNotify) format_item_free);
+ p->status_format = form;
+ }
+ }
+
+ panel_update_cols (WIDGET (p), p->frame_size);
+
+ if (retcode)
+ message (D_ERROR, _("Warning"),
+ _("User supplied format looks invalid, reverting to default."));
+ if (retcode & 0x01)
+ {
+ g_free (p->user_format);
+ p->user_format = g_strdup (DEFAULT_USER_FORMAT);
+ }
+ if (retcode & 0x02)
+ {
+ g_free (p->user_status_format[p->list_format]);
+ p->user_status_format[p->list_format] = g_strdup (DEFAULT_USER_FORMAT);
+ }
+
+ return retcode;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_filter (WPanel * panel, const file_filter_t * filter)
+{
+ MC_PTR_FREE (panel->filter.value);
+ mc_search_free (panel->filter.handler);
+ panel->filter.handler = NULL;
+
+ /* NULL to clear filter */
+ if (filter != NULL)
+ panel->filter = *filter;
+
+ reread_cmd ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Select current item and readjust the panel */
+void
+select_item (WPanel * panel)
+{
+ adjust_top_file (panel);
+
+ panel->dirty = TRUE;
+
+ execute_hooks (select_file_hook);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Clears all files in the panel, used only when one file was marked */
+void
+unmark_files (WPanel * panel)
+{
+ if (panel->marked != 0)
+ {
+ int i;
+
+ for (i = 0; i < panel->dir.len; i++)
+ file_mark (panel, i, 0);
+
+ panel->dirs_marked = 0;
+ panel->marked = 0;
+ panel->total = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Recalculate the panels summary information, used e.g. when marked
+ files might have been removed by an external command */
+
+void
+recalculate_panel_summary (WPanel * panel)
+{
+ int i;
+
+ panel->marked = 0;
+ panel->dirs_marked = 0;
+ panel->total = 0;
+
+ for (i = 0; i < panel->dir.len; i++)
+ if (panel->dir.list[i].f.marked != 0)
+ {
+ /* do_file_mark will return immediately if newmark == oldmark.
+ So we have to first unmark it to get panel's summary information
+ updated. (Norbert) */
+ panel->dir.list[i].f.marked = 0;
+ do_file_mark (panel, i, 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This routine marks a file or a directory */
+
+void
+do_file_mark (WPanel * panel, int idx, int mark)
+{
+ if (panel->dir.list[idx].f.marked == mark)
+ return;
+
+ /* Only '..' can't be marked, '.' isn't visible */
+ if (DIR_IS_DOTDOT (panel->dir.list[idx].fname->str))
+ return;
+
+ file_mark (panel, idx, mark);
+ if (panel->dir.list[idx].f.marked != 0)
+ {
+ panel->marked++;
+
+ if (S_ISDIR (panel->dir.list[idx].st.st_mode))
+ {
+ if (panel->dir.list[idx].f.dir_size_computed != 0)
+ panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
+ panel->dirs_marked++;
+ }
+ else
+ panel->total += (uintmax_t) panel->dir.list[idx].st.st_size;
+
+ set_colors (panel);
+ }
+ else
+ {
+ if (S_ISDIR (panel->dir.list[idx].st.st_mode))
+ {
+ if (panel->dir.list[idx].f.dir_size_computed != 0)
+ panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
+ panel->dirs_marked--;
+ }
+ else
+ panel->total -= (uintmax_t) panel->dir.list[idx].st.st_size;
+
+ panel->marked--;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Changes the current directory of the panel.
+ * Record change in the directory history.
+ */
+gboolean
+panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type)
+{
+ gboolean r;
+
+ r = panel_do_cd_int (panel, new_dir_vpath, cd_type);
+ if (r)
+ directory_history_add (panel, panel->cwd_vpath);
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+file_mark (WPanel * panel, int lc_index, int val)
+{
+ if (panel->dir.list[lc_index].f.marked != val)
+ {
+ panel->dir.list[lc_index].f.marked = val;
+ panel->dirty = TRUE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_re_sort (WPanel * panel)
+{
+ char *filename;
+ const file_entry_t *fe;
+ int i;
+
+ if (panel == NULL)
+ return;
+
+ fe = panel_current_entry (panel);
+ filename = g_strndup (fe->fname->str, fe->fname->len);
+ unselect_item (panel);
+ dir_list_sort (&panel->dir, panel->sort_field->sort_routine, &panel->sort_info);
+ panel->current = -1;
+
+ for (i = panel->dir.len; i != 0; i--)
+ if (strcmp (panel->dir.list[i - 1].fname->str, filename) == 0)
+ {
+ panel->current = i - 1;
+ break;
+ }
+
+ g_free (filename);
+ panel->top = panel->current - panel_items (panel) / 2;
+ select_item (panel);
+ panel->dirty = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order)
+{
+ if (sort_order == NULL)
+ return;
+
+ panel->sort_field = sort_order;
+
+ /* The directory is already sorted, we have to load the unsorted stuff */
+ if (sort_order->sort_routine == (GCompareFunc) unsorted)
+ {
+ char *current_file;
+ const GString *fname;
+
+ fname = panel_current_entry (panel)->fname;
+ current_file = g_strndup (fname->str, fname->len);
+ panel_reload (panel);
+ panel_set_current_by_name (panel, current_file);
+ g_free (current_file);
+ }
+ panel_re_sort (panel);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef HAVE_CHARSET
+
+/**
+ * Change panel encoding.
+ * @param panel WPanel object
+ */
+
+void
+panel_change_encoding (WPanel * panel)
+{
+ const char *encoding = NULL;
+ char *errmsg;
+ int r;
+
+ r = select_charset (-1, -1, panel->codepage, FALSE);
+
+ if (r == SELECT_CHARSET_CANCEL)
+ return; /* Cancel */
+
+ panel->codepage = r;
+
+ if (panel->codepage == SELECT_CHARSET_NO_TRANSLATE)
+ {
+ /* No translation */
+ vfs_path_t *cd_path_vpath;
+
+ g_free (init_translation_table (mc_global.display_codepage, mc_global.display_codepage));
+ cd_path_vpath = remove_encoding_from_path (panel->cwd_vpath);
+ panel_do_cd (panel, cd_path_vpath, cd_parse_command);
+ show_dir (panel);
+ vfs_path_free (cd_path_vpath, TRUE);
+ return;
+ }
+
+ errmsg = init_translation_table (panel->codepage, mc_global.display_codepage);
+ if (errmsg != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, "%s", errmsg);
+ g_free (errmsg);
+ return;
+ }
+
+ encoding = get_codepage_id (panel->codepage);
+ if (encoding != NULL)
+ {
+ vfs_path_change_encoding (panel->cwd_vpath, encoding);
+
+ if (!panel_do_cd (panel, panel->cwd_vpath, cd_parse_command))
+ cd_error_message (vfs_path_as_str (panel->cwd_vpath));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Remove encode info from last path element.
+ *
+ */
+vfs_path_t *
+remove_encoding_from_path (const vfs_path_t * vpath)
+{
+ vfs_path_t *ret_vpath;
+ GString *tmp_conv;
+ int indx;
+
+ ret_vpath = vfs_path_new (FALSE);
+
+ tmp_conv = g_string_new ("");
+
+ for (indx = 0; indx < vfs_path_elements_count (vpath); indx++)
+ {
+ GIConv converter;
+ vfs_path_element_t *path_element;
+
+ path_element = vfs_path_element_clone (vfs_path_get_by_index (vpath, indx));
+
+ if (path_element->encoding == NULL)
+ {
+ vfs_path_add_element (ret_vpath, path_element);
+ continue;
+ }
+
+ converter = str_crt_conv_to (path_element->encoding);
+ if (converter == INVALID_CONV)
+ {
+ vfs_path_add_element (ret_vpath, path_element);
+ continue;
+ }
+
+ MC_PTR_FREE (path_element->encoding);
+
+ str_vfs_convert_from (converter, path_element->path, tmp_conv);
+
+ g_free (path_element->path);
+ path_element->path = g_strndup (tmp_conv->str, tmp_conv->len);
+
+ g_string_set_size (tmp_conv, 0);
+
+ str_close_conv (converter);
+ str_close_conv (path_element->dir.converter);
+ path_element->dir.converter = INVALID_CONV;
+ vfs_path_add_element (ret_vpath, path_element);
+ }
+ g_string_free (tmp_conv, TRUE);
+ return ret_vpath;
+}
+#endif /* HAVE_CHARSET */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * This routine reloads the directory in both panels. It tries to
+ * select current_file in current_panel and other_file in other_panel.
+ * If current_file == -1 then it automatically sets current_file and
+ * other_file to the current files in the panels.
+ *
+ * If flags has the UP_ONLY_CURRENT bit toggled on, then it
+ * will not reload the other panel.
+ *
+ * @param flags for reload panel
+ * @param current_file name of the current file
+ */
+
+void
+update_panels (panel_update_flags_t flags, const char *current_file)
+{
+ WPanel *panel;
+
+ /* first, update other panel... */
+ if ((flags & UP_ONLY_CURRENT) == 0)
+ update_one_panel (get_other_index (), flags, UP_KEEPSEL);
+ /* ...then current one */
+ update_one_panel (get_current_index (), flags, current_file);
+
+ if (get_current_type () == view_listing)
+ panel = PANEL (get_panel_widget (get_current_index ()));
+ else
+ panel = PANEL (get_panel_widget (get_other_index ()));
+
+ if (!panel->is_panelized)
+ (void) mc_chdir (panel->cwd_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gsize
+panel_get_num_of_sortable_fields (void)
+{
+ gsize ret = 0, lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].is_user_choice)
+ ret++;
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char **
+panel_get_sortable_fields (gsize * array_size)
+{
+ char **ret;
+ gsize lc_index, i;
+
+ lc_index = panel_get_num_of_sortable_fields ();
+
+ ret = g_try_new0 (char *, lc_index + 1);
+ if (ret == NULL)
+ return NULL;
+
+ if (array_size != NULL)
+ *array_size = lc_index;
+
+ lc_index = 0;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (panel_fields[i].is_user_choice)
+ ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_id (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].id != NULL && strcmp (name, panel_fields[lc_index].id) == 0)
+ return &panel_fields[lc_index];
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_title_hotkey (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].title_hotkey != NULL &&
+ strcmp (name, _(panel_fields[lc_index].title_hotkey)) == 0)
+ return &panel_fields[lc_index];
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+const panel_field_t *
+panel_get_field_by_title (const char *name)
+{
+ gsize lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ {
+ const char *title;
+
+ title = panel_get_title_without_hotkey (panel_fields[lc_index].title_hotkey);
+ if (strcmp (title, name) == 0)
+ return &panel_fields[lc_index];
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gsize
+panel_get_num_of_user_possible_fields (void)
+{
+ gsize ret = 0, lc_index;
+
+ for (lc_index = 0; panel_fields[lc_index].id != NULL; lc_index++)
+ if (panel_fields[lc_index].use_in_user_format)
+ ret++;
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char **
+panel_get_user_possible_fields (gsize * array_size)
+{
+ char **ret;
+ gsize lc_index, i;
+
+ lc_index = panel_get_num_of_user_possible_fields ();
+
+ ret = g_try_new0 (char *, lc_index + 1);
+ if (ret == NULL)
+ return NULL;
+
+ if (array_size != NULL)
+ *array_size = lc_index;
+
+ lc_index = 0;
+
+ for (i = 0; panel_fields[i].id != NULL; i++)
+ if (panel_fields[i].use_in_user_format)
+ ret[lc_index++] = g_strdup (_(panel_fields[i].title_hotkey));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_panelize_cd (void)
+{
+ WPanel *panel;
+ int i;
+ dir_list *list;
+ panelized_descr_t *pdescr;
+ dir_list *plist;
+ gboolean panelized_same;
+
+ if (!SELECTED_IS_PANEL)
+ create_panel (MENU_PANEL_IDX, view_listing);
+
+ panel = PANEL (get_panel_widget (MENU_PANEL_IDX));
+
+ dir_list_clean (&panel->dir);
+
+ if (panel->panelized_descr == NULL)
+ panel->panelized_descr = panelized_descr_new ();
+
+ pdescr = panel->panelized_descr;
+ plist = &pdescr->list;
+
+ if (pdescr->root_vpath == NULL)
+ panel_panelize_change_root (panel, panel->cwd_vpath);
+
+ if (plist->len < 1)
+ dir_list_init (plist);
+ else if (plist->len > panel->dir.size)
+ dir_list_grow (&panel->dir, plist->len - panel->dir.size);
+
+ list = &panel->dir;
+ list->len = plist->len;
+
+ panelized_same = vfs_path_equal (pdescr->root_vpath, panel->cwd_vpath);
+
+ for (i = 0; i < plist->len; i++)
+ {
+ if (panelized_same || DIR_IS_DOTDOT (plist->list[i].fname->str))
+ list->list[i].fname = mc_g_string_dup (plist->list[i].fname);
+ else
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath =
+ vfs_path_append_new (pdescr->root_vpath, plist->list[i].fname->str, (char *) NULL);
+ list->list[i].fname = g_string_new (vfs_path_as_str (tmp_vpath));
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+ list->list[i].f.link_to_dir = plist->list[i].f.link_to_dir;
+ list->list[i].f.stale_link = plist->list[i].f.stale_link;
+ list->list[i].f.dir_size_computed = plist->list[i].f.dir_size_computed;
+ list->list[i].f.marked = plist->list[i].f.marked;
+ list->list[i].st = plist->list[i].st;
+ list->list[i].name_sort_key = plist->list[i].name_sort_key;
+ list->list[i].extension_sort_key = plist->list[i].extension_sort_key;
+ }
+
+ panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (panel);
+
+ panel_set_current_by_name (panel, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Change root directory of panelized content.
+ * @param panel file panel
+ * @param new_root new path
+ */
+void
+panel_panelize_change_root (WPanel * panel, const vfs_path_t * new_root)
+{
+ if (panel->panelized_descr == NULL)
+ panel->panelized_descr = panelized_descr_new ();
+ else
+ vfs_path_free (panel->panelized_descr->root_vpath, TRUE);
+
+ panel->panelized_descr->root_vpath = vfs_path_clone (new_root);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Conditionally switches a panel's directory to "/" (root).
+ *
+ * If a panelized panel's listing contain absolute paths, this function
+ * sets the panel's directory to "/". Otherwise it does nothing.
+ *
+ * Rationale:
+ *
+ * This makes tokenized strings like "%d/%p" work. This also makes other
+ * places work where such naive concatenation is done in code (e.g., when
+ * pressing ctrl+shift+enter, for CK_PutCurrentFullSelected).
+ *
+ * When to call:
+ *
+ * You should always call this function after you populate the listing
+ * of a panelized panel.
+ */
+void
+panel_panelize_absolutize_if_needed (WPanel * panel)
+{
+ const dir_list *const list = &panel->dir;
+
+ /* Note: We don't support mixing of absolute and relative paths, which is
+ * why it's ok for us to check only the 1st entry. */
+ if (list->len > 1 && g_path_is_absolute (list->list[1].fname->str))
+ {
+ vfs_path_t *root;
+
+ root = vfs_path_from_str (PATH_SEP_STR);
+ panel_set_cwd (panel, root);
+ if (panel == current_panel)
+ mc_chdir (root);
+ vfs_path_free (root, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_panelize_save (WPanel * panel)
+{
+ int i;
+ dir_list *list = &panel->dir;
+ dir_list *plist;
+
+ panel_panelize_change_root (panel, panel->cwd_vpath);
+
+ plist = &panel->panelized_descr->list;
+
+ if (plist->len > 0)
+ dir_list_clean (plist);
+ if (panel->dir.len == 0)
+ return;
+
+ if (panel->dir.len > plist->size)
+ dir_list_grow (plist, panel->dir.len - plist->size);
+ plist->len = panel->dir.len;
+
+ for (i = 0; i < panel->dir.len; i++)
+ {
+ plist->list[i].fname = mc_g_string_dup (list->list[i].fname);
+ plist->list[i].f.link_to_dir = list->list[i].f.link_to_dir;
+ plist->list[i].f.stale_link = list->list[i].f.stale_link;
+ plist->list[i].f.dir_size_computed = list->list[i].f.dir_size_computed;
+ plist->list[i].f.marked = list->list[i].f.marked;
+ plist->list[i].st = list->list[i].st;
+ plist->list[i].name_sort_key = list->list[i].name_sort_key;
+ plist->list[i].extension_sort_key = list->list[i].extension_sort_key;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_init (void)
+{
+ panel_sort_up_char = mc_skin_get ("widget-panel", "sort-up-char", "'");
+ panel_sort_down_char = mc_skin_get ("widget-panel", "sort-down-char", ".");
+ panel_hiddenfiles_show_char = mc_skin_get ("widget-panel", "hiddenfiles-show-char", ".");
+ panel_hiddenfiles_hide_char = mc_skin_get ("widget-panel", "hiddenfiles-hide-char", ".");
+ panel_history_prev_item_char = mc_skin_get ("widget-panel", "history-prev-item-char", "<");
+ panel_history_next_item_char = mc_skin_get ("widget-panel", "history-next-item-char", ">");
+ panel_history_show_list_char = mc_skin_get ("widget-panel", "history-show-list-char", "^");
+ panel_filename_scroll_left_char =
+ mc_skin_get ("widget-panel", "filename-scroll-left-char", "{");
+ panel_filename_scroll_right_char =
+ mc_skin_get ("widget-panel", "filename-scroll-right-char", "}");
+
+ string_file_name_buffer = g_string_sized_new (MC_MAXFILENAMELEN);
+
+ mc_event_add (MCEVENT_GROUP_FILEMANAGER, "update_panels", event_update_panels, NULL, NULL);
+ mc_event_add (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file",
+ panel_save_current_file_to_clip_file, NULL, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+panel_deinit (void)
+{
+ g_free (panel_sort_up_char);
+ g_free (panel_sort_down_char);
+ g_free (panel_hiddenfiles_show_char);
+ g_free (panel_hiddenfiles_hide_char);
+ g_free (panel_history_prev_item_char);
+ g_free (panel_history_next_item_char);
+ g_free (panel_history_show_list_char);
+ g_free (panel_filename_scroll_left_char);
+ g_free (panel_filename_scroll_right_char);
+ g_string_free (string_file_name_buffer, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum exact)
+{
+ gboolean res;
+ const vfs_path_t *_new_dir_vpath = new_dir_vpath;
+
+ if (panel->is_panelized)
+ {
+ size_t new_vpath_len;
+
+ new_vpath_len = vfs_path_len (new_dir_vpath);
+ if (vfs_path_equal_len (new_dir_vpath, panel->panelized_descr->root_vpath, new_vpath_len))
+ _new_dir_vpath = panel->panelized_descr->root_vpath;
+ }
+
+ res = panel_do_cd (panel, _new_dir_vpath, exact);
+
+#ifdef HAVE_CHARSET
+ if (res)
+ {
+ const vfs_path_element_t *path_element;
+
+ path_element = vfs_path_get_by_index (panel->cwd_vpath, -1);
+ if (path_element->encoding != NULL)
+ panel->codepage = get_codepage_index (path_element->encoding);
+ else
+ panel->codepage = SELECT_CHARSET_NO_TRANSLATE;
+ }
+#endif /* HAVE_CHARSET */
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/panel.h b/src/filemanager/panel.h
new file mode 100644
index 0000000..5bfc36c
--- /dev/null
+++ b/src/filemanager/panel.h
@@ -0,0 +1,285 @@
+/** \file panel.h
+ * \brief Header: defines WPanel structure
+ */
+
+#ifndef MC__PANEL_H
+#define MC__PANEL_H
+
+#include <inttypes.h> /* uintmax_t */
+#include <limits.h> /* MB_LEN_MAX */
+
+#include "lib/global.h" /* gboolean */
+#include "lib/fs.h" /* MC_MAXPATHLEN */
+#include "lib/strutil.h"
+#include "lib/widget.h" /* Widget */
+#include "lib/filehighlight.h"
+#include "lib/file-entry.h"
+
+#include "dir.h" /* dir_list */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define PANEL(x) ((WPanel *)(x))
+#define DEFAULT_USER_FORMAT "half type name | size | perm"
+
+#define LIST_FORMATS 4
+
+#define UP_KEEPSEL ((char *) -1)
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ list_full, /* Name, size, perm/date */
+ list_brief, /* Name */
+ list_long, /* Like ls -l */
+ list_user /* User defined */
+} list_format_t;
+
+typedef enum
+{
+ frame_full, /* full screen frame */
+ frame_half /* half screen frame */
+} panel_display_t;
+
+typedef enum
+{
+ UP_OPTIMIZE = 0,
+ UP_RELOAD = 1,
+ UP_ONLY_CURRENT = 2
+} panel_update_flags_t;
+
+/* run mode and params */
+enum cd_enum
+{
+ cd_parse_command,
+ cd_exact
+};
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct panel_field_struct
+{
+ const char *id;
+ int min_size;
+ gboolean expands;
+ align_crt_t default_just;
+ const char *hotkey;
+ const char *title_hotkey;
+ gboolean is_user_choice;
+ gboolean use_in_user_format;
+ const char *(*string_fn) (file_entry_t *, int);
+ GCompareFunc sort_routine; /* used by mouse_sort_col() */
+} panel_field_t;
+
+typedef struct
+{
+ dir_list list;
+ vfs_path_t *root_vpath;
+} panelized_descr_t;
+
+typedef struct
+{
+ Widget widget;
+
+ char *name; /* The panel name */
+
+ panel_display_t frame_size; /* half or full frame */
+
+ gboolean active; /* If panel is currently selected */
+ gboolean dirty; /* Should we redisplay the panel? */
+
+ gboolean is_panelized; /* Panelization: special mode, can't reload the file list */
+ panelized_descr_t *panelized_descr; /* Panelization descriptor */
+
+#ifdef HAVE_CHARSET
+ int codepage; /* Panel codepage */
+#endif
+
+ dir_list dir; /* Directory contents */
+ struct stat dir_stat; /* Stat of current dir: used by execute () */
+
+ vfs_path_t *cwd_vpath; /* Current Working Directory */
+ vfs_path_t *lwd_vpath; /* Last Working Directory */
+
+ list_format_t list_format; /* Listing type */
+ GSList *format; /* Display format */
+ char *user_format; /* User format */
+ int list_cols; /* Number of file list columns */
+ int brief_cols; /* Number of columns in case of list_brief format */
+ /* sort */
+ dir_sort_options_t sort_info;
+ const panel_field_t *sort_field;
+
+ int marked; /* Count of marked files */
+ int dirs_marked; /* Count of marked directories */
+ uintmax_t total; /* Bytes in marked files */
+
+ int top; /* The file shown on the top of the panel */
+ int current; /* Index to the currently selected file */
+
+ GSList *status_format; /* Mini status format */
+ gboolean user_mini_status; /* Is user_status_format used */
+ char *user_status_format[LIST_FORMATS]; /* User format for status line */
+
+ file_filter_t filter; /* File name filter */
+
+ struct
+ {
+ char *name; /* Directory history name for history file */
+ GList *list; /* Directory history */
+ GList *current; /* Pointer to the current history item */
+ } dir_history;
+
+ struct
+ {
+ gboolean active;
+ GString *buffer;
+ GString *prev_buffer;
+ char ch[MB_LEN_MAX]; /* Buffer for multi-byte character */
+ int chpoint; /* Point after last characters in @ch */
+ } quick_search;
+
+ int content_shift; /* Number of characters of filename need to skip from left side. */
+ int max_shift; /* Max shift for visible part of current panel */
+} WPanel;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern hook_t *select_file_hook;
+
+extern mc_fhl_t *mc_filehighlight;
+
+/*** declarations of public functions ************************************************************/
+
+WPanel *panel_sized_empty_new (const char *panel_name, int y, int x, int lines, int cols);
+WPanel *panel_sized_with_dir_new (const char *panel_name, int y, int x, int lines, int cols,
+ const vfs_path_t * vpath);
+
+void panel_clean_dir (WPanel * panel);
+
+void panel_reload (WPanel * panel);
+void panel_set_sort_order (WPanel * panel, const panel_field_t * sort_order);
+void panel_re_sort (WPanel * panel);
+
+#ifdef HAVE_CHARSET
+void panel_change_encoding (WPanel * panel);
+vfs_path_t *remove_encoding_from_path (const vfs_path_t * vpath);
+#endif
+
+void update_panels (panel_update_flags_t flags, const char *current_file);
+int set_panel_formats (WPanel * p);
+
+void panel_set_filter (WPanel * panel, const file_filter_t * filter);
+
+void panel_set_current_by_name (WPanel * panel, const char *name);
+
+void unmark_files (WPanel * panel);
+void select_item (WPanel * panel);
+
+void recalculate_panel_summary (WPanel * panel);
+void file_mark (WPanel * panel, int idx, int val);
+void do_file_mark (WPanel * panel, int idx, int val);
+
+gboolean panel_do_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type);
+gboolean panel_cd (WPanel * panel, const vfs_path_t * new_dir_vpath, enum cd_enum cd_type);
+
+gsize panel_get_num_of_sortable_fields (void);
+char **panel_get_sortable_fields (gsize * array_size);
+const panel_field_t *panel_get_field_by_id (const char *name);
+const panel_field_t *panel_get_field_by_title (const char *name);
+const panel_field_t *panel_get_field_by_title_hotkey (const char *name);
+gsize panel_get_num_of_user_possible_fields (void);
+char **panel_get_user_possible_fields (gsize * array_size);
+void panel_set_cwd (WPanel * panel, const vfs_path_t * vpath);
+void panel_set_lwd (WPanel * panel, const vfs_path_t * vpath);
+
+void panel_panelize_cd (void);
+void panel_panelize_change_root (WPanel * panel, const vfs_path_t * new_root);
+void panel_panelize_absolutize_if_needed (WPanel * panel);
+void panel_panelize_save (WPanel * panel);
+
+void panel_init (void);
+void panel_deinit (void);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Empty panel creation.
+ *
+ * @param panel_name name of panel for setup retrieving
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_empty_new (const char *panel_name)
+{
+ /* Unknown sizes of the panel at startup */
+ return panel_sized_empty_new (panel_name, 0, 0, 1, 1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation for specified directory.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param vpath working panel directory. If NULL then current directory is used
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_with_dir_new (const char *panel_name, const vfs_path_t * vpath)
+{
+ /* Unknown sizes of the panel at startup */
+ return panel_sized_with_dir_new (panel_name, 0, 0, 1, 1, vpath);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation.
+ *
+ * @param panel_name name of panel for setup retrieving
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_new (const char *panel_name)
+{
+ return panel_with_dir_new (panel_name, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Panel creation with specified size.
+ *
+ * @param panel_name name of panel for setup retrieving
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines vertical size
+ * @param cols horizontal size
+ *
+ * @return new instance of WPanel
+ */
+
+static inline WPanel *
+panel_sized_new (const char *panel_name, int y, int x, int lines, int cols)
+{
+ return panel_sized_with_dir_new (panel_name, y, x, lines, cols, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline file_entry_t *
+panel_current_entry (const WPanel * panel)
+{
+ return &(panel->dir.list[panel->current]);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__PANEL_H */
diff --git a/src/filemanager/panelize.c b/src/filemanager/panelize.c
new file mode 100644
index 0000000..e90076c
--- /dev/null
+++ b/src/filemanager/panelize.c
@@ -0,0 +1,573 @@
+/*
+ External panelize
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1995
+ Jakub Jelinek, 1995
+ Andrew Borodin <aborodin@vmail.ru> 2011-2023
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file panelize.c
+ * \brief Source: External panelization module
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "lib/skin.h"
+#include "lib/tty/tty.h"
+#include "lib/vfs/vfs.h"
+#include "lib/mcconfig.h" /* Load/save directories panelize */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/util.h" /* mc_pipe_t */
+
+#include "src/history.h"
+
+#include "filemanager.h" /* current_panel */
+#include "layout.h" /* rotate_dash() */
+#include "panel.h" /* WPanel, dir.h */
+
+#include "panelize.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define UX 3
+#define UY 2
+
+#define B_ADD B_USER
+#define B_REMOVE (B_USER + 1)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ char *command;
+ char *label;
+} panelize_entry_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WListbox *l_panelize;
+static WDialog *panelize_dlg;
+static int last_listitem;
+static WInput *pname;
+static GSList *panelize = NULL;
+
+static const char *panelize_section = "Panelize";
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelize_entry_free (gpointer data)
+{
+ panelize_entry_t *entry = (panelize_entry_t *) data;
+
+ g_free (entry->command);
+ g_free (entry->label);
+ g_free (entry);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+panelize_entry_cmp_by_label (gconstpointer a, gconstpointer b)
+{
+ const panelize_entry_t *entry = (const panelize_entry_t *) a;
+ const char *label = (const char *) b;
+
+ return strcmp (entry->label, label);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+panelize_entry_add_to_listbox (gpointer data, gpointer user_data)
+{
+ panelize_entry_t *entry = (panelize_entry_t *) data;
+
+ (void) user_data;
+
+ listbox_add_item (l_panelize, LISTBOX_APPEND_AT_END, 0, entry->label, entry, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+update_command (void)
+{
+ if (l_panelize->current != last_listitem)
+ {
+ panelize_entry_t *data = NULL;
+
+ last_listitem = l_panelize->current;
+ listbox_get_current (l_panelize, NULL, (void **) &data);
+ input_assign_text (pname, data->command);
+ pname->point = 0;
+ input_update (pname, TRUE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+panelize_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ group_default_callback (w, NULL, MSG_INIT, 0, NULL);
+ MC_FALLTHROUGH;
+
+ case MSG_NOTIFY: /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
+ update_command ();
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+external_panelize_init (void)
+{
+ struct
+ {
+ int ret_cmd;
+ button_flags_t flags;
+ const char *text;
+ } panelize_but[] =
+ {
+ /* *INDENT-OFF* */
+ { B_ENTER, DEFPUSH_BUTTON, N_("Pane&lize") },
+ { B_REMOVE, NORMAL_BUTTON, N_("&Remove") },
+ { B_ADD, NORMAL_BUTTON, N_("&Add new") },
+ { B_CANCEL, NORMAL_BUTTON, N_("&Cancel") }
+ /* *INDENT-ON* */
+ };
+
+ WGroup *g;
+
+ size_t i;
+ int blen;
+ int panelize_cols;
+ int x, y;
+
+ last_listitem = 0;
+
+ do_refresh ();
+
+ i = G_N_ELEMENTS (panelize_but);
+ blen = i - 1; /* gaps between buttons */
+ while (i-- != 0)
+ {
+#ifdef ENABLE_NLS
+ panelize_but[i].text = _(panelize_but[i].text);
+#endif
+ blen += str_term_width1 (panelize_but[i].text) + 3 + 1;
+ if (panelize_but[i].flags == DEFPUSH_BUTTON)
+ blen += 2;
+ }
+
+ panelize_cols = COLS - 6;
+ panelize_cols = MAX (panelize_cols, blen + 4);
+
+ panelize_dlg =
+ dlg_create (TRUE, 0, 0, 20, panelize_cols, WPOS_CENTER, FALSE, dialog_colors,
+ panelize_callback, NULL, "[External panelize]", _("External panelize"));
+ g = GROUP (panelize_dlg);
+
+ /* add listbox to the dialogs */
+ y = UY;
+ group_add_widget (g, groupbox_new (y++, UX, 12, panelize_cols - UX * 2, ""));
+
+ l_panelize = listbox_new (y, UX + 1, 10, panelize_cols - UX * 2 - 2, FALSE, NULL);
+ g_slist_foreach (panelize, panelize_entry_add_to_listbox, NULL);
+ listbox_set_current (l_panelize, listbox_search_text (l_panelize, _("Other command")));
+ group_add_widget (g, l_panelize);
+
+ y += WIDGET (l_panelize)->rect.lines + 1;
+ group_add_widget (g, label_new (y++, UX, _("Command")));
+ pname =
+ input_new (y++, UX, input_colors, panelize_cols - UX * 2, "", "in",
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_HOSTNAMES | INPUT_COMPLETE_COMMANDS |
+ INPUT_COMPLETE_VARIABLES | INPUT_COMPLETE_USERNAMES | INPUT_COMPLETE_CD |
+ INPUT_COMPLETE_SHELL_ESC);
+ group_add_widget (g, pname);
+
+ group_add_widget (g, hline_new (y++, -1, -1));
+
+ x = (panelize_cols - blen) / 2;
+ for (i = 0; i < G_N_ELEMENTS (panelize_but); i++)
+ {
+ WButton *b;
+
+ b = button_new (y, x,
+ panelize_but[i].ret_cmd, panelize_but[i].flags, panelize_but[i].text, NULL);
+ group_add_widget (g, b);
+
+ x += button_get_len (b) + 1;
+ }
+
+ widget_select (WIDGET (l_panelize));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+external_panelize_done (void)
+{
+ widget_destroy (WIDGET (panelize_dlg));
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add2panelize (char *label, char *command)
+{
+ panelize_entry_t *entry;
+
+ entry = g_try_new (panelize_entry_t, 1);
+ if (entry != NULL)
+ {
+ entry->label = label;
+ entry->command = command;
+
+ panelize = g_slist_insert_sorted (panelize, entry, panelize_entry_cmp_by_label);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add2panelize_cmd (void)
+{
+ if (!input_is_empty (pname))
+ {
+ char *label;
+
+ label = input_dialog (_("Add to external panelize"),
+ _("Enter command label:"), MC_HISTORY_FM_PANELIZE_ADD, "",
+ INPUT_COMPLETE_NONE);
+ if (label == NULL || *label == '\0')
+ g_free (label);
+ else
+ add2panelize (label, input_get_text (pname));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_from_panelize (panelize_entry_t * entry)
+{
+ if (strcmp (entry->label, _("Other command")) != 0)
+ {
+ panelize = g_slist_remove (panelize, entry);
+ panelize_entry_free (entry);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_external_panelize (const char *command)
+{
+ dir_list *list = &current_panel->dir;
+ mc_pipe_t *external;
+ GError *error = NULL;
+ GString *remain_file_name = NULL;
+
+ external = mc_popen (command, TRUE, TRUE, &error);
+ if (external == NULL)
+ {
+ message (D_ERROR, _("External panelize"), "%s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Clear the counters and the directory list */
+ panel_clean_dir (current_panel);
+
+ panel_panelize_change_root (current_panel, current_panel->cwd_vpath);
+
+ dir_list_init (list);
+
+ while (TRUE)
+ {
+ GString *line;
+ gboolean ok;
+
+ /* init buffers before call of mc_pread() */
+ external->out.len = MC_PIPE_BUFSIZE;
+ external->err.len = MC_PIPE_BUFSIZE;
+ external->err.null_term = TRUE;
+
+ mc_pread (external, &error);
+
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), error->message);
+ g_error_free (error);
+ break;
+ }
+
+ if (external->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("External panelize:\n%s"), external->err.buf);
+
+ if (external->out.len == MC_PIPE_STREAM_EOF)
+ break;
+
+ if (external->out.len == 0)
+ continue;
+
+ if (external->out.len == MC_PIPE_ERROR_READ)
+ {
+ message (D_ERROR, MSG_ERROR,
+ _("External panelize:\nfailed to read data from child stdout:\n%s"),
+ unix_error_string (external->out.error));
+ break;
+ }
+
+ ok = TRUE;
+
+ while (ok && (line = mc_pstream_get_string (&external->out)) != NULL)
+ {
+ char *name;
+ gboolean link_to_dir, stale_link;
+ struct stat st;
+
+ /* handle a \n-separated file list */
+
+ if (line->str[line->len - 1] == '\n')
+ {
+ /* entire file name or last chunk */
+
+ g_string_truncate (line, line->len - 1);
+
+ /* join filename chunks */
+ if (remain_file_name != NULL)
+ {
+ g_string_append_len (remain_file_name, line->str, line->len);
+ g_string_free (line, TRUE);
+ line = remain_file_name;
+ remain_file_name = NULL;
+ }
+ }
+ else
+ {
+ /* first or middle chunk of file name */
+
+ if (remain_file_name == NULL)
+ remain_file_name = line;
+ else
+ {
+ g_string_append_len (remain_file_name, line->str, line->len);
+ g_string_free (line, TRUE);
+ }
+
+ continue;
+ }
+
+ name = line->str;
+
+ if (name[0] == '.' && IS_PATH_SEP (name[1]))
+ name += 2;
+
+ if (handle_path (name, &st, &link_to_dir, &stale_link))
+ {
+ ok = dir_list_append (list, name, &st, link_to_dir, stale_link);
+
+ if (ok)
+ {
+ file_mark (current_panel, list->len - 1, 0);
+
+ if ((list->len & 31) == 0)
+ rotate_dash (TRUE);
+ }
+ }
+
+ g_string_free (line, TRUE);
+ }
+ }
+
+ if (remain_file_name != NULL)
+ g_string_free (remain_file_name, TRUE);
+
+ mc_pclose (external, NULL);
+
+ current_panel->is_panelized = TRUE;
+ panel_panelize_absolutize_if_needed (current_panel);
+
+ panel_set_current_by_name (current_panel, NULL);
+ panel_re_sort (current_panel);
+ rotate_dash (FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_cmd (void)
+{
+ if (!vfs_current_is_local ())
+ {
+ message (D_ERROR, MSG_ERROR, _("Cannot run external panelize in a non-local directory"));
+ return;
+ }
+
+ external_panelize_init ();
+
+ /* display file info */
+ tty_setcolor (SELECTED_COLOR);
+
+ switch (dlg_run (panelize_dlg))
+ {
+ case B_CANCEL:
+ break;
+
+ case B_ADD:
+ add2panelize_cmd ();
+ break;
+
+ case B_REMOVE:
+ {
+ panelize_entry_t *entry;
+
+ listbox_get_current (l_panelize, NULL, (void **) &entry);
+ remove_from_panelize (entry);
+ break;
+ }
+
+ case B_ENTER:
+ if (!input_is_empty (pname))
+ {
+ char *cmd;
+
+ cmd = input_get_text (pname);
+ widget_destroy (WIDGET (panelize_dlg));
+ do_external_panelize (cmd);
+ g_free (cmd);
+ repaint_screen ();
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ external_panelize_done ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_load (void)
+{
+ char **keys;
+
+ keys = mc_config_get_keys (mc_global.main_config, panelize_section, NULL);
+
+ add2panelize (g_strdup (_("Other command")), g_strdup (""));
+
+ if (*keys == NULL)
+ {
+ add2panelize (g_strdup (_("Modified git files")), g_strdup ("git ls-files --modified"));
+ add2panelize (g_strdup (_("Find rejects after patching")),
+ g_strdup ("find . -name \\*.rej -print"));
+ add2panelize (g_strdup (_("Find *.orig after patching")),
+ g_strdup ("find . -name \\*.orig -print"));
+ add2panelize (g_strdup (_("Find SUID and SGID programs")),
+ g_strdup
+ ("find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print"));
+ }
+ else
+ {
+ GIConv conv;
+ char **profile_keys;
+
+ conv = str_crt_conv_from ("UTF-8");
+
+ for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
+ {
+ GString *buffer;
+
+ if (mc_global.utf8_display || conv == INVALID_CONV)
+ buffer = g_string_new (*profile_keys);
+ else
+ {
+ buffer = g_string_new ("");
+ if (str_convert (conv, *profile_keys, buffer) == ESTR_FAILURE)
+ g_string_assign (buffer, *profile_keys);
+ }
+
+ add2panelize (g_string_free (buffer, FALSE),
+ mc_config_get_string (mc_global.main_config, panelize_section,
+ *profile_keys, ""));
+ }
+
+ str_close_conv (conv);
+ }
+
+ g_strfreev (keys);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_save (void)
+{
+ GSList *l;
+
+ mc_config_del_group (mc_global.main_config, panelize_section);
+
+ for (l = panelize; l != NULL; l = g_slist_next (l))
+ {
+ panelize_entry_t *current = (panelize_entry_t *) l->data;
+
+ if (strcmp (current->label, _("Other command")) != 0)
+ mc_config_set_string (mc_global.main_config,
+ panelize_section, current->label, current->command);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+external_panelize_free (void)
+{
+ g_clear_slist (&panelize, panelize_entry_free);
+ panelize = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/panelize.h b/src/filemanager/panelize.h
new file mode 100644
index 0000000..eacf976
--- /dev/null
+++ b/src/filemanager/panelize.h
@@ -0,0 +1,25 @@
+/** \file panelize.h
+ * \brief Header: External panelization module
+ */
+
+#ifndef MC__PANELIZE_H
+#define MC__PANELIZE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void external_panelize_cmd (void);
+void external_panelize_load (void);
+void external_panelize_save (void);
+void external_panelize_free (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__PANELIZE_H */
diff --git a/src/filemanager/tree.c b/src/filemanager/tree.c
new file mode 100644
index 0000000..fd50407
--- /dev/null
+++ b/src/filemanager/tree.c
@@ -0,0 +1,1345 @@
+/*
+ Directory tree browser for the Midnight Commander
+ This module has been converted to be a widget.
+
+ The program load and saves the tree each time the tree widget is
+ created and destroyed. This is required for the future vfs layer,
+ it will be possible to have tree views over virtual file systems.
+
+ Copyright (C) 1994-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1996
+ Norbert Warmuth, 1997
+ Miguel de Icaza, 1996, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file tree.c
+ * \brief Source: directory tree browser
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h"
+#include "lib/skin.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+#include "src/setup.h" /* confirm_delete, panels_options */
+#include "src/keymap.h"
+#include "src/history.h"
+
+#include "dir.h"
+#include "filemanager.h" /* the_menubar */
+#include "file.h" /* copy_dir_dir(), move_dir_dir(), erase_dir() */
+#include "layout.h" /* command_prompt */
+#include "treestore.h"
+#include "cmd.h"
+#include "filegui.h"
+#include "cd.h" /* cd_error_message() */
+
+#include "tree.h"
+
+/*** global variables ****************************************************************************/
+
+/* The pointer to the tree */
+WTree *the_tree = NULL;
+
+/* If this is true, then when browsing the tree the other window will
+ * automatically reload it's directory with the contents of the currently
+ * selected directory.
+ */
+gboolean xtree_mode = FALSE;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define tlines(t) (t->is_panel ? WIDGET (t)->rect.lines - 2 - \
+ (panels_options.show_mini_info ? 2 : 0) : WIDGET (t)->rect.lines)
+
+/*** file scope type declarations ****************************************************************/
+
+struct WTree
+{
+ Widget widget;
+ struct TreeStore *store;
+ tree_entry *selected_ptr; /* The selected directory */
+ GString *search_buffer; /* Current search string */
+ tree_entry **tree_shown; /* Entries currently on screen */
+ gboolean is_panel; /* panel or plain widget flag */
+ gboolean searching; /* Are we on searching mode? */
+ int topdiff; /* The difference between the topmost
+ shown and the selected */
+};
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static void tree_rescan (void *data);
+
+/*** file scope variables ************************************************************************/
+
+/* Specifies the display mode: 1d or 2d */
+static gboolean tree_navigation_flag = FALSE;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+back_ptr (tree_entry * ptr, int *count)
+{
+ int i;
+
+ for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++)
+ ;
+
+ *count = i;
+ return ptr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+forw_ptr (tree_entry * ptr, int *count)
+{
+ int i;
+
+ for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++)
+ ;
+
+ *count = i;
+ return ptr;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+remove_callback (tree_entry * entry, void *data)
+{
+ WTree *tree = data;
+
+ if (tree->selected_ptr == entry)
+ {
+ if (tree->selected_ptr->next != NULL)
+ tree->selected_ptr = tree->selected_ptr->next;
+ else
+ tree->selected_ptr = tree->selected_ptr->prev;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Save the ${XDG_CACHE_HOME}/mc/Tree file */
+
+static void
+save_tree (WTree * tree)
+{
+ int error;
+
+ (void) tree;
+
+ error = tree_store_save ();
+ if (error != 0)
+ {
+ char *tree_name;
+
+ tree_name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ fprintf (stderr, _("Cannot open the %s file for writing:\n%s\n"), tree_name,
+ unix_error_string (error));
+ g_free (tree_name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_remove_entry (WTree * tree, const vfs_path_t * name_vpath)
+{
+ (void) tree;
+ tree_store_remove_entry (name_vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_destroy (WTree * tree)
+{
+ tree_store_remove_entry_remove_hook (remove_callback);
+ save_tree (tree);
+
+ MC_PTR_FREE (tree->tree_shown);
+ g_string_free (tree->search_buffer, TRUE);
+ tree->selected_ptr = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Loads the .mc.tree file */
+
+static void
+load_tree (WTree * tree)
+{
+ vfs_path_t *vpath;
+
+ tree_store_load ();
+
+ tree->selected_ptr = tree->store->tree_first;
+ vpath = vfs_path_from_str (mc_config_get_home_dir ());
+ tree_chdir (tree, vpath);
+ vfs_path_free (vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_show_mini_info (WTree * tree, int tree_lines, int tree_cols)
+{
+ Widget *w = WIDGET (tree);
+ int line;
+
+ /* Show mini info */
+ if (tree->is_panel)
+ {
+ if (!panels_options.show_mini_info)
+ return;
+ line = tree_lines + 2;
+ }
+ else
+ line = tree_lines + 1;
+
+ if (tree->searching)
+ {
+ /* Show search string */
+ tty_setcolor (INPUT_COLOR);
+ tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
+ widget_gotoyx (w, line, 1);
+ tty_print_char (PATH_SEP);
+ tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT));
+ tty_print_char (' ');
+ }
+ else
+ {
+ /* Show full name of selected directory */
+
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
+ tty_draw_hline (w->rect.y + line, w->rect.x + 1, ' ', tree_cols);
+ widget_gotoyx (w, line, 1);
+ tty_print_string (str_fit_to_term
+ (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+show_tree (WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+ tree_entry *current;
+ int i, j;
+ int topsublevel = 0;
+ int x = 0, y = 0;
+ int tree_lines, tree_cols;
+
+ /* Initialize */
+ tree_lines = tlines (tree);
+ tree_cols = w->rect.cols;
+
+ widget_gotoyx (w, y, x);
+ if (tree->is_panel)
+ {
+ tree_cols -= 2;
+ x = y = 1;
+ }
+
+ g_free (tree->tree_shown);
+ tree->tree_shown = g_new0 (tree_entry *, tree_lines);
+
+ if (tree->store->tree_first != NULL)
+ topsublevel = tree->store->tree_first->sublevel;
+
+ if (tree->selected_ptr == NULL)
+ {
+ tree->selected_ptr = tree->store->tree_first;
+ tree->topdiff = 0;
+ }
+ current = tree->selected_ptr;
+
+ /* Calculate the directory which is to be shown on the topmost line */
+ if (!tree_navigation_flag)
+ current = back_ptr (current, &tree->topdiff);
+ else
+ {
+ i = 0;
+
+ while (current->prev != NULL && i < tree->topdiff)
+ {
+ current = current->prev;
+
+ if (current->sublevel < tree->selected_ptr->sublevel)
+ {
+ if (vfs_path_equal (current->name, tree->selected_ptr->name))
+ i++;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
+ ;
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ i++;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel + 1)
+ {
+ j = vfs_path_len (tree->selected_ptr->name);
+ if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ i++;
+ }
+ }
+ tree->topdiff = i;
+ }
+
+ /* Loop for every line */
+ for (i = 0; i < tree_lines; i++)
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
+
+ /* Move to the beginning of the line */
+ tty_draw_hline (w->rect.y + y + i, w->rect.x + x, ' ', tree_cols);
+
+ if (current == NULL)
+ continue;
+
+ if (tree->is_panel)
+ {
+ gboolean selected;
+
+ selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr;
+ tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR);
+ }
+ else
+ {
+ int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL;
+
+ tty_setcolor (colors[idx]);
+ }
+
+ tree->tree_shown[i] = current;
+ if (current->sublevel == topsublevel)
+ /* Show full name */
+ tty_print_string (str_fit_to_term
+ (vfs_path_as_str (current->name),
+ tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT));
+ else
+ {
+ /* Sub level directory */
+ tty_set_alt_charset (TRUE);
+
+ /* Output branch parts */
+ for (j = 0; j < current->sublevel - topsublevel - 1; j++)
+ {
+ if (tree_cols - 8 - 3 * j < 9)
+ break;
+ tty_print_char (' ');
+ if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
+ tty_print_char (ACS_VLINE);
+ else
+ tty_print_char (' ');
+ tty_print_char (' ');
+ }
+ tty_print_char (' ');
+ j++;
+ if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
+ tty_print_char (ACS_LLCORNER);
+ else
+ tty_print_char (ACS_LTEE);
+ tty_print_char (ACS_HLINE);
+ tty_set_alt_charset (FALSE);
+
+ /* Show sub-name */
+ tty_print_char (' ');
+ tty_print_string (str_fit_to_term
+ (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
+ }
+
+ /* Calculate the next value for current */
+ current = current->next;
+ if (tree_navigation_flag)
+ for (; current != NULL; current = current->next)
+ {
+ if (current->sublevel < tree->selected_ptr->sublevel)
+ {
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
+ vfs_path_len (current->name)))
+ break;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
+ ;
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
+ break;
+ }
+ else if (current->sublevel == tree->selected_ptr->sublevel + 1
+ && vfs_path_len (tree->selected_ptr->name) > 1)
+ {
+ if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
+ vfs_path_len (tree->selected_ptr->name)))
+ break;
+ }
+ }
+ }
+
+ tree_show_mini_info (tree, tree_lines, tree_cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_check_focus (WTree * tree)
+{
+ if (tree->topdiff < 3)
+ tree->topdiff = 3;
+ else if (tree->topdiff >= tlines (tree) - 3)
+ tree->topdiff = tlines (tree) - 3 - 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_backward (WTree * tree, int i)
+{
+ if (!tree_navigation_flag)
+ tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
+ else
+ {
+ tree_entry *current;
+ int j = 0;
+
+ current = tree->selected_ptr;
+ while (j < i && current->prev != NULL
+ && current->prev->sublevel >= tree->selected_ptr->sublevel)
+ {
+ current = current->prev;
+ if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ j++;
+ }
+ }
+ i = j;
+ }
+
+ tree->topdiff -= i;
+ tree_check_focus (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_forward (WTree * tree, int i)
+{
+ if (!tree_navigation_flag)
+ tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
+ else
+ {
+ tree_entry *current;
+ int j = 0;
+
+ current = tree->selected_ptr;
+ while (j < i && current->next != NULL
+ && current->next->sublevel >= tree->selected_ptr->sublevel)
+ {
+ current = current->next;
+ if (current->sublevel == tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ j++;
+ }
+ }
+ i = j;
+ }
+
+ tree->topdiff += i;
+ tree_check_focus (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_child (WTree * tree)
+{
+ tree_entry *current;
+
+ /* Do we have a starting point? */
+ if (tree->selected_ptr == NULL)
+ return;
+
+ /* Take the next entry */
+ current = tree->selected_ptr->next;
+ /* Is it the child of the selected entry */
+ if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
+ {
+ /* Yes -> select this entry */
+ tree->selected_ptr = current;
+ tree->topdiff++;
+ tree_check_focus (tree);
+ }
+ else
+ {
+ /* No -> rescan and try again */
+ tree_rescan (tree);
+ current = tree->selected_ptr->next;
+ if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
+ {
+ tree->selected_ptr = current;
+ tree->topdiff++;
+ tree_check_focus (tree);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_to_parent (WTree * tree)
+{
+ tree_entry *current;
+ tree_entry *old;
+
+ if (tree->selected_ptr == NULL)
+ return FALSE;
+
+ old = tree->selected_ptr;
+
+ for (current = tree->selected_ptr->prev;
+ current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
+ current = current->prev)
+ tree->topdiff--;
+
+ if (current == NULL)
+ current = tree->store->tree_first;
+ tree->selected_ptr = current;
+ tree_check_focus (tree);
+ return tree->selected_ptr != old;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_top (WTree * tree)
+{
+ tree->selected_ptr = tree->store->tree_first;
+ tree->topdiff = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_to_bottom (WTree * tree)
+{
+ tree->selected_ptr = tree->store->tree_last;
+ tree->topdiff = tlines (tree) - 3 - 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_chdir_sel (WTree * tree)
+{
+ if (tree->is_panel)
+ {
+ WPanel *p;
+
+ p = change_panel ();
+
+ if (panel_cd (p, tree->selected_ptr->name, cd_exact))
+ select_item (p);
+ else
+ cd_error_message (vfs_path_as_str (tree->selected_ptr->name));
+
+ widget_draw (WIDGET (p));
+ (void) change_panel ();
+ show_tree (tree);
+ }
+ else
+ {
+ WDialog *h = DIALOG (WIDGET (tree)->owner);
+
+ h->ret_value = B_ENTER;
+ dlg_close (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+maybe_chdir (WTree * tree)
+{
+ if (xtree_mode && tree->is_panel && is_idle ())
+ tree_chdir_sel (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Search tree for text */
+
+static gboolean
+search_tree (WTree * tree, const GString * text)
+{
+ tree_entry *current = tree->selected_ptr;
+ gboolean wrapped = FALSE;
+ gboolean found = FALSE;
+
+ while (!found && (!wrapped || current != tree->selected_ptr))
+ if (strncmp (current->subname, text->str, text->len) == 0)
+ {
+ tree->selected_ptr = current;
+ found = TRUE;
+ }
+ else
+ {
+ current = current->next;
+ if (current == NULL)
+ {
+ current = tree->store->tree_first;
+ wrapped = TRUE;
+ }
+
+ tree->topdiff++;
+ }
+
+ tree_check_focus (tree);
+ return found;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_do_search (WTree * tree, int key)
+{
+ /* TODO: support multi-byte characters, see do_search() in panel.c */
+
+ if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
+ g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
+ else if (key != 0)
+ g_string_append_c (tree->search_buffer, (gchar) key);
+
+ if (!search_tree (tree, tree->search_buffer))
+ g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
+
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_rescan (void *data)
+{
+ WTree *tree = data;
+ vfs_path_t *old_vpath;
+
+ old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
+ if (old_vpath == NULL)
+ return;
+
+ if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
+ {
+ int ret;
+
+ tree_store_rescan (tree->selected_ptr->name);
+ ret = mc_chdir (old_vpath);
+ (void) ret;
+ }
+ vfs_path_free (old_vpath, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_forget (void *data)
+{
+ WTree *tree = data;
+
+ if (tree->selected_ptr != NULL)
+ tree_remove_entry (tree, tree->selected_ptr->name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_copy (WTree * tree, const char *default_dest)
+{
+ char msg[BUF_MEDIUM];
+ char *dest;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ g_snprintf (msg, sizeof (msg), _("Copy \"%s\" directory to:"),
+ str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
+ dest = input_expand_dialog (Q_ ("DialogTitle|Copy"),
+ msg, MC_HISTORY_FM_TREE_COPY, default_dest,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (dest != NULL && *dest != '\0')
+ {
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ ctx = file_op_context_new (OP_COPY);
+ tctx = file_op_total_context_new ();
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
+ tctx->ask_overwrite = FALSE;
+ copy_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE,
+ FALSE, NULL);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+ }
+
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move (WTree * tree, const char *default_dest)
+{
+ char msg[BUF_MEDIUM];
+ char *dest;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ g_snprintf (msg, sizeof (msg), _("Move \"%s\" directory to:"),
+ str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
+ dest =
+ input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
+ INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
+
+ if (dest != NULL && *dest != '\0')
+ {
+ vfs_path_t *dest_vpath;
+ struct stat buf;
+
+ dest_vpath = vfs_path_from_str (dest);
+
+ if (mc_stat (dest_vpath, &buf))
+ message (D_ERROR, MSG_ERROR, _("Cannot stat the destination\n%s"),
+ unix_error_string (errno));
+ else if (!S_ISDIR (buf.st_mode))
+ file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), dest);
+ else
+ {
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ ctx = file_op_context_new (OP_MOVE);
+ tctx = file_op_total_context_new ();
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
+ move_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+ }
+
+ vfs_path_free (dest_vpath, TRUE);
+ }
+
+ g_free (dest);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#if 0
+static void
+tree_mkdir (WTree * tree)
+{
+ char old_dir[MC_MAXPATHLEN];
+
+ if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
+ return;
+ /* FIXME
+ mkdir_cmd (tree);
+ */
+ tree_rescan (tree);
+ chdir (old_dir);
+}
+#endif
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_rmdir (void *data)
+{
+ WTree *tree = data;
+ file_op_context_t *ctx;
+ file_op_total_context_t *tctx;
+
+ if (tree->selected_ptr == NULL)
+ return;
+
+ if (confirm_delete)
+ {
+ char *buf;
+ int result;
+
+ buf = g_strdup_printf (_("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
+
+ result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _("&Yes"), _("&No"));
+ g_free (buf);
+ if (result != 0)
+ return;
+ }
+
+ ctx = file_op_context_new (OP_DELETE);
+ tctx = file_op_total_context_new ();
+
+ file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
+ if (erase_dir (tctx, ctx, tree->selected_ptr->name) == FILE_CONT)
+ tree_forget (tree);
+ file_op_total_context_destroy (tctx);
+ file_op_context_destroy (ctx);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_up (WTree * tree)
+{
+ tree_move_backward (tree, 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_down (WTree * tree)
+{
+ tree_move_forward (tree, 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_home (WTree * tree)
+{
+ tree_move_to_top (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_move_end (WTree * tree)
+{
+ tree_move_to_bottom (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_pgup (WTree * tree)
+{
+ tree_move_backward (tree, tlines (tree) - 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_move_pgdn (WTree * tree)
+{
+ tree_move_forward (tree, tlines (tree) - 1);
+ show_tree (tree);
+ maybe_chdir (tree);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_left (WTree * tree)
+{
+ gboolean v = FALSE;
+
+ if (tree_navigation_flag)
+ {
+ v = tree_move_to_parent (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+tree_move_right (WTree * tree)
+{
+ gboolean v = FALSE;
+
+ if (tree_navigation_flag)
+ {
+ tree_move_to_child (tree);
+ show_tree (tree);
+ maybe_chdir (tree);
+ v = TRUE;
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_start_search (WTree * tree)
+{
+ if (tree->searching)
+ {
+ if (tree->selected_ptr == tree->store->tree_last)
+ tree_move_to_top (tree);
+ else
+ {
+ gboolean i;
+
+ /* set navigation mode temporarily to 'Static' because in
+ * dynamic navigation mode tree_move_forward will not move
+ * to a lower sublevel if necessary (sequent searches must
+ * start with the directory followed the last found directory)
+ */
+ i = tree_navigation_flag;
+ tree_navigation_flag = FALSE;
+ tree_move_forward (tree, 1);
+ tree_navigation_flag = i;
+ }
+ tree_do_search (tree, 0);
+ }
+ else
+ {
+ tree->searching = TRUE;
+ g_string_set_size (tree->search_buffer, 0);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_toggle_navig (WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+ WButtonBar *b;
+
+ tree_navigation_flag = !tree_navigation_flag;
+
+ b = buttonbar_find (DIALOG (w->owner));
+ buttonbar_set_label (b, 4,
+ tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
+ w->keymap, w);
+ widget_draw (WIDGET (b));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_execute_cmd (WTree * tree, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ if (command != CK_Search)
+ tree->searching = FALSE;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, "[Directory Tree]" };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+ case CK_Forget:
+ tree_forget (tree);
+ break;
+ case CK_ToggleNavigation:
+ tree_toggle_navig (tree);
+ break;
+ case CK_Copy:
+ tree_copy (tree, "");
+ break;
+ case CK_Move:
+ tree_move (tree, "");
+ break;
+ case CK_Up:
+ tree_move_up (tree);
+ break;
+ case CK_Down:
+ tree_move_down (tree);
+ break;
+ case CK_Top:
+ tree_move_home (tree);
+ break;
+ case CK_Bottom:
+ tree_move_end (tree);
+ break;
+ case CK_PageUp:
+ tree_move_pgup (tree);
+ break;
+ case CK_PageDown:
+ tree_move_pgdn (tree);
+ break;
+ case CK_Enter:
+ tree_chdir_sel (tree);
+ break;
+ case CK_Reread:
+ tree_rescan (tree);
+ break;
+ case CK_Search:
+ tree_start_search (tree);
+ break;
+ case CK_Delete:
+ tree_rmdir (tree);
+ break;
+ case CK_Quit:
+ if (!tree->is_panel)
+ dlg_close (DIALOG (WIDGET (tree)->owner));
+ return res;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ show_tree (tree);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_key (WTree * tree, int key)
+{
+ long command;
+
+ if (is_abort_char (key))
+ {
+ if (tree->is_panel)
+ {
+ tree->searching = FALSE;
+ show_tree (tree);
+ return MSG_HANDLED; /* eat abort char */
+ }
+ /* modal tree dialog: let upper layer see the
+ abort character and close the dialog */
+ return MSG_NOT_HANDLED;
+ }
+
+ if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ tree_do_search (tree, key);
+ show_tree (tree);
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (tree), key);
+ switch (command)
+ {
+ case CK_IgnoreKey:
+ break;
+ case CK_Left:
+ return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
+ case CK_Right:
+ return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
+ default:
+ tree_execute_cmd (tree, command);
+ return MSG_HANDLED;
+ }
+
+ /* Do not eat characters not meant for the tree below ' ' (e.g. C-l). */
+ if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
+ {
+ tree_start_search (tree);
+ tree_do_search (tree, key);
+ return MSG_HANDLED;
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_frame (WDialog * h, WTree * tree)
+{
+ Widget *w = WIDGET (tree);
+
+ (void) h;
+
+ tty_setcolor (NORMAL_COLOR);
+ widget_erase (w);
+ if (tree->is_panel)
+ {
+ const char *title = _("Directory tree");
+ const int len = str_term_width1 (title);
+
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, FALSE);
+
+ widget_gotoyx (w, 0, (w->rect.cols - len - 2) / 2);
+ tty_printf (" %s ", title);
+
+ if (panels_options.show_mini_info)
+ {
+ int y;
+
+ y = w->rect.lines - 3;
+ widget_gotoyx (w, y, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, y, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ tty_draw_hline (w->rect.y + y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WTree *tree = (WTree *) w;
+ WDialog *h = DIALOG (w->owner);
+ WButtonBar *b;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ tree_frame (h, tree);
+ show_tree (tree);
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ b = buttonbar_find (h);
+ widget_draw (WIDGET (b));
+ }
+ return MSG_HANDLED;
+
+ case MSG_FOCUS:
+ b = buttonbar_find (h);
+ buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w);
+ buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w);
+ buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w);
+ buttonbar_set_label (b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static")
+ : Q_ ("ButtonBar|Dynamc"), w->keymap, w);
+ buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
+ buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w);
+#if 0
+ /* FIXME: mkdir is currently defunct */
+ buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w);
+#else
+ buttonbar_clear_label (b, 7, w);
+#endif
+ buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w);
+
+ return MSG_HANDLED;
+
+ case MSG_UNFOCUS:
+ tree->searching = FALSE;
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return tree_key (tree, parm);
+
+ case MSG_ACTION:
+ /* command from buttonbar */
+ return tree_execute_cmd (tree, parm);
+
+ case MSG_DESTROY:
+ tree_destroy (tree);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Mouse callback
+ */
+static void
+tree_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WTree *tree = (WTree *) w;
+ int y;
+
+ y = event->y;
+ if (tree->is_panel)
+ y--;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ /* rest of the upper frame - call menu */
+ if (tree->is_panel && event->y == WIDGET (w->owner)->rect.y)
+ {
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ }
+ else if (!widget_get_state (w, WST_FOCUSED))
+ (void) change_panel ();
+ break;
+
+ case MSG_MOUSE_CLICK:
+ {
+ int lines;
+
+ lines = tlines (tree);
+
+ if (y < 0)
+ {
+ tree_move_backward (tree, lines - 1);
+ show_tree (tree);
+ }
+ else if (y >= lines)
+ {
+ tree_move_forward (tree, lines - 1);
+ show_tree (tree);
+ }
+ else if ((event->count & GPM_DOUBLE) != 0)
+ {
+ if (tree->tree_shown[y] != NULL)
+ {
+ tree->selected_ptr = tree->tree_shown[y];
+ tree->topdiff = y;
+ }
+
+ tree_chdir_sel (tree);
+ }
+ }
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ case MSG_MOUSE_SCROLL_DOWN:
+ /* TODO: Ticket #2218 */
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WTree *
+tree_new (int y, int x, int lines, int cols, gboolean is_panel)
+{
+ WRect r = { y, x, lines, cols };
+ WTree *tree;
+ Widget *w;
+
+ tree = g_new (WTree, 1);
+
+ w = WIDGET (tree);
+ widget_init (w, &r, tree_callback, tree_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->keymap = tree_map;
+
+ tree->is_panel = is_panel;
+ tree->selected_ptr = NULL;
+
+ tree->store = tree_store_get ();
+ tree_store_add_entry_remove_hook (remove_callback, tree);
+ tree->tree_shown = NULL;
+ tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN);
+ tree->topdiff = w->rect.lines / 2;
+ tree->searching = FALSE;
+
+ load_tree (tree);
+ return tree;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_chdir (WTree * tree, const vfs_path_t * dir)
+{
+ tree_entry *current;
+
+ current = tree_store_whereis (dir);
+ if (current != NULL)
+ {
+ tree->selected_ptr = current;
+ tree_check_focus (tree);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Return name of the currently selected entry */
+
+const vfs_path_t *
+tree_selected_name (const WTree * tree)
+{
+ return tree->selected_ptr->name;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+sync_tree (const vfs_path_t * vpath)
+{
+ tree_chdir (the_tree, vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WTree *
+find_tree (const WDialog * h)
+{
+ return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/tree.h b/src/filemanager/tree.h
new file mode 100644
index 0000000..f1dbba6
--- /dev/null
+++ b/src/filemanager/tree.h
@@ -0,0 +1,35 @@
+/** \file tree.h
+ * \brief Header: directory tree browser
+ */
+
+#ifndef MC__TREE_H
+#define MC__TREE_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WTree WTree;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WTree *the_tree;
+extern gboolean xtree_mode;
+
+/*** declarations of public functions ************************************************************/
+
+WTree *tree_new (int y, int x, int lines, int cols, gboolean is_panel);
+
+void tree_chdir (WTree * tree, const vfs_path_t * dir);
+const vfs_path_t *tree_selected_name (const WTree * tree);
+
+void sync_tree (const vfs_path_t * vpath);
+
+WTree *find_tree (const WDialog * h);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__TREE_H */
diff --git a/src/filemanager/treestore.c b/src/filemanager/treestore.c
new file mode 100644
index 0000000..2d23c93
--- /dev/null
+++ b/src/filemanager/treestore.c
@@ -0,0 +1,941 @@
+/*
+ Tree Store
+ Contains a storage of the file system tree representation
+
+ This module has been converted to be a widget.
+
+ The program load and saves the tree each time the tree widget is
+ created and destroyed. This is required for the future vfs layer,
+ it will be possible to have tree views over virtual file systems.
+
+ Copyright (C) 1999-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Janne Kukonlehto, 1994, 1996
+ Norbert Warmuth, 1997
+ Miguel de Icaza, 1996, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+ Andrew Borodin <aborodin@vmail.ru>, 2013
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file treestore.c
+ * \brief Source: tree store
+ *
+ * Contains a storage of the file system tree representation.
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+#include "lib/mcconfig.h"
+#include "lib/vfs/vfs.h"
+#include "lib/fileloc.h"
+#include "lib/strescape.h"
+#include "lib/hook.h"
+#include "lib/util.h"
+
+#include "src/setup.h" /* setup_init() */
+
+#include "treestore.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define TREE_SIGNATURE "Midnight Commander TreeStore v 2.0"
+
+/*** file scope type declarations ****************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static tree_entry *tree_store_add_entry (const vfs_path_t * name);
+
+/*** file scope variables ************************************************************************/
+
+static struct TreeStore ts;
+
+static hook_t *remove_entry_hooks;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+tree_store_dirty (gboolean dirty)
+{
+ ts.dirty = dirty;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ *
+ * @return the number of common bytes in the strings.
+ */
+
+static size_t
+str_common (const vfs_path_t * s1_vpath, const vfs_path_t * s2_vpath)
+{
+ size_t result = 0;
+ const char *s1, *s2;
+
+ s1 = vfs_path_as_str (s1_vpath);
+ s2 = vfs_path_as_str (s2_vpath);
+
+ while (*s1 != '\0' && *s2 != '\0' && *s1++ == *s2++)
+ result++;
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** The directory names are arranged in a single linked list in the same
+ * order as they are displayed. When the tree is displayed the expected
+ * order is like this:
+ * /
+ * /bin
+ * /etc
+ * /etc/X11
+ * /etc/rc.d
+ * /etc.old/X11
+ * /etc.old/rc.d
+ * /usr
+ *
+ * i.e. the required collating sequence when comparing two directory names is
+ * '\0' < PATH_SEP < all-other-characters-in-encoding-order
+ *
+ * Since strcmp doesn't fulfil this requirement we use pathcmp when
+ * inserting directory names into the list. The meaning of the return value
+ * of pathcmp and strcmp are the same (an integer less than, equal to, or
+ * greater than zero if p1 is found to be less than, to match, or be greater
+ * than p2.
+ */
+
+static int
+pathcmp (const vfs_path_t * p1_vpath, const vfs_path_t * p2_vpath)
+{
+ int ret_val;
+ const char *p1, *p2;
+
+ p1 = vfs_path_as_str (p1_vpath);
+ p2 = vfs_path_as_str (p2_vpath);
+
+ for (; *p1 == *p2; p1++, p2++)
+ if (*p1 == '\0')
+ return 0;
+
+ if (*p1 == '\0')
+ ret_val = -1;
+ else if (*p2 == '\0')
+ ret_val = 1;
+ else if (IS_PATH_SEP (*p1))
+ ret_val = -1;
+ else if (IS_PATH_SEP (*p2))
+ ret_val = 1;
+ else
+ ret_val = (*p1 - *p2);
+
+ return ret_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+decode (char *buffer)
+{
+ char *res, *p, *q;
+
+ res = g_strdup (buffer);
+
+ for (p = q = res; *p != '\0'; p++, q++)
+ {
+ if (*p == '\n')
+ {
+ *q = '\0';
+ return res;
+ }
+
+ if (*p != '\\')
+ {
+ *q = *p;
+ continue;
+ }
+
+ p++;
+
+ switch (*p)
+ {
+ case 'n':
+ *q = '\n';
+ break;
+ case '\\':
+ *q = '\\';
+ break;
+ default:
+ break;
+ }
+ }
+
+ *q = *p;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Loads the tree store from the specified filename */
+
+static int
+tree_store_load_from (const char *name)
+{
+ FILE *file;
+ char buffer[MC_MAXPATHLEN + 20];
+
+ g_return_val_if_fail (name != NULL, 0);
+
+ if (ts.loaded)
+ return 1;
+
+ file = fopen (name, "r");
+
+ if (file != NULL
+ && (fgets (buffer, sizeof (buffer), file) == NULL
+ || strncmp (buffer, TREE_SIGNATURE, strlen (TREE_SIGNATURE)) != 0))
+ {
+ fclose (file);
+ file = NULL;
+ }
+
+ if (file != NULL)
+ {
+ char oldname[MC_MAXPATHLEN] = "\0";
+
+ ts.loaded = TRUE;
+
+ /* File open -> read contents */
+ while (fgets (buffer, MC_MAXPATHLEN, file))
+ {
+ tree_entry *e;
+ gboolean scanned;
+ char *lc_name;
+
+ /* Skip invalid records */
+ if (buffer[0] != '0' && buffer[0] != '1')
+ continue;
+
+ if (buffer[1] != ':')
+ continue;
+
+ scanned = buffer[0] == '1';
+
+ lc_name = decode (buffer + 2);
+ if (!IS_PATH_SEP (lc_name[0]))
+ {
+ /* Clear-text decompression */
+ char *s;
+
+ s = strtok (lc_name, " ");
+ if (s != NULL)
+ {
+ char *different;
+ int common;
+
+ common = atoi (s);
+ different = strtok (NULL, "");
+ if (different != NULL)
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (oldname);
+ g_strlcpy (oldname + common, different, sizeof (oldname) - (size_t) common);
+ if (vfs_file_is_local (vpath))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (oldname);
+ e = tree_store_add_entry (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ }
+ }
+ }
+ else
+ {
+ vfs_path_t *vpath;
+
+ vpath = vfs_path_from_str (lc_name);
+ if (vfs_file_is_local (vpath))
+ {
+ e = tree_store_add_entry (vpath);
+ e->scanned = scanned;
+ }
+ vfs_path_free (vpath, TRUE);
+ g_strlcpy (oldname, lc_name, sizeof (oldname));
+ }
+ g_free (lc_name);
+ }
+
+ fclose (file);
+ }
+
+ /* Nothing loaded, we add some standard directories */
+ if (!ts.tree_first)
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_from_str (PATH_SEP_STR);
+ tree_store_add_entry (tmp_vpath);
+ tree_store_rescan (tmp_vpath);
+ vfs_path_free (tmp_vpath, TRUE);
+ ts.loaded = TRUE;
+ }
+
+ return 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+encode (const vfs_path_t * vpath, size_t offset)
+{
+ return strutils_escape (vfs_path_as_str (vpath) + offset, -1, "\n\\", FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Saves the tree to the specified filename */
+
+static int
+tree_store_save_to (char *name)
+{
+ tree_entry *current;
+ FILE *file;
+
+ file = fopen (name, "w");
+ if (file == NULL)
+ return errno;
+
+ fprintf (file, "%s\n", TREE_SIGNATURE);
+
+ for (current = ts.tree_first; current != NULL; current = current->next)
+ if (vfs_file_is_local (current->name))
+ {
+ int i, common;
+
+ /* Clear-text compression */
+ if (current->prev != NULL
+ && (common = str_common (current->prev->name, current->name)) > 2)
+ {
+ char *encoded;
+
+ encoded = encode (current->name, common);
+ i = fprintf (file, "%d:%d %s\n", current->scanned ? 1 : 0, common, encoded);
+ g_free (encoded);
+ }
+ else
+ {
+ char *encoded;
+
+ encoded = encode (current->name, 0);
+ i = fprintf (file, "%d:%s\n", current->scanned ? 1 : 0, encoded);
+ g_free (encoded);
+ }
+
+ if (i == EOF)
+ {
+ fprintf (stderr, _("Cannot write to the %s file:\n%s\n"),
+ name, unix_error_string (errno));
+ break;
+ }
+ }
+
+ tree_store_dirty (FALSE);
+ fclose (file);
+
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+tree_store_add_entry (const vfs_path_t * name)
+{
+ int flag = -1;
+ tree_entry *current;
+ tree_entry *old = NULL;
+ tree_entry *new;
+ int submask = 0;
+
+ if (ts.tree_last != NULL && ts.tree_last->next != NULL)
+ abort ();
+
+ /* Search for the correct place */
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ old = current;
+
+ if (flag == 0)
+ return current; /* Already in the list */
+
+ /* Not in the list -> add it */
+ new = g_new0 (tree_entry, 1);
+ if (current == NULL)
+ {
+ /* Append to the end of the list */
+ if (ts.tree_first == NULL)
+ {
+ /* Empty list */
+ ts.tree_first = new;
+ new->prev = NULL;
+ }
+ else
+ {
+ if (old != NULL)
+ old->next = new;
+ new->prev = old;
+ }
+ new->next = NULL;
+ ts.tree_last = new;
+ }
+ else
+ {
+ /* Insert in to the middle of the list */
+ new->prev = old;
+ if (old != NULL)
+ {
+ /* Yes, in the middle */
+ new->next = old->next;
+ old->next = new;
+ }
+ else
+ {
+ /* Nope, in the beginning of the list */
+ new->next = ts.tree_first;
+ ts.tree_first = new;
+ }
+ new->next->prev = new;
+ }
+
+ /* Calculate attributes */
+ new->name = vfs_path_clone (name);
+ new->sublevel = vfs_path_tokens_count (new->name);
+
+ {
+ const char *new_name;
+
+ new_name = vfs_path_get_last_path_str (new->name);
+ new->subname = strrchr (new_name, PATH_SEP);
+ if (new->subname == NULL)
+ new->subname = new_name;
+ else
+ new->subname++;
+ }
+
+ if (new->next != NULL)
+ submask = new->next->submask;
+
+ submask |= 1 << new->sublevel;
+ submask &= (2 << new->sublevel) - 1;
+ new->submask = submask;
+ new->mark = FALSE;
+
+ /* Correct the submasks of the previous entries */
+ for (current = new->prev;
+ current != NULL && current->sublevel > new->sublevel; current = current->prev)
+ current->submask |= 1 << new->sublevel;
+
+ tree_store_dirty (TRUE);
+ return new;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+tree_store_notify_remove (tree_entry * entry)
+{
+ hook_t *p;
+
+ for (p = remove_entry_hooks; p != NULL; p = p->next)
+ {
+ tree_store_remove_fn r = (tree_store_remove_fn) p->hook_fn;
+
+ r (entry, p->hook_data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static tree_entry *
+remove_entry (tree_entry * entry)
+{
+ tree_entry *current = entry->prev;
+ long submask = 0;
+ tree_entry *ret = NULL;
+
+ tree_store_notify_remove (entry);
+
+ /* Correct the submasks of the previous entries */
+ if (entry->next != NULL)
+ submask = entry->next->submask;
+
+ for (; current != NULL && current->sublevel > entry->sublevel; current = current->prev)
+ {
+ submask |= 1 << current->sublevel;
+ submask &= (2 << current->sublevel) - 1;
+ current->submask = submask;
+ }
+
+ /* Unlink the entry from the list */
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ ts.tree_first = entry->next;
+
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ else
+ ts.tree_last = entry->prev;
+
+ /* Free the memory used by the entry */
+ vfs_path_free (entry->name, TRUE);
+ g_free (entry);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+process_special_dirs (GList ** special_dirs, const char *file)
+{
+ gchar **start_buff;
+ mc_config_t *cfg;
+
+ cfg = mc_config_init (file, TRUE);
+ if (cfg == NULL)
+ return;
+
+ start_buff = mc_config_get_string_list (cfg, "Special dirs", "list", NULL);
+ if (start_buff != NULL)
+ {
+ gchar **buffers;
+
+ for (buffers = start_buff; *buffers != NULL; buffers++)
+ {
+ *special_dirs = g_list_prepend (*special_dirs, *buffers);
+ *buffers = NULL;
+ }
+
+ g_strfreev (start_buff);
+ }
+ mc_config_deinit (cfg);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+should_skip_directory (const vfs_path_t * vpath)
+{
+ static GList *special_dirs = NULL;
+ GList *l;
+ static gboolean loaded = FALSE;
+ gboolean ret = FALSE;
+
+ if (!loaded)
+ {
+ const char *profile_name;
+
+ profile_name = setup_init ();
+ process_special_dirs (&special_dirs, profile_name);
+ process_special_dirs (&special_dirs, mc_global.profile_name);
+
+ loaded = TRUE;
+ }
+
+ for (l = special_dirs; l != NULL; l = g_list_next (l))
+ if (strncmp (vfs_path_as_str (vpath), l->data, strlen (l->data)) == 0)
+ {
+ ret = TRUE;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+queue_vpath_free (gpointer data)
+{
+ vfs_path_free ((vfs_path_t *) data, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* Searches for specified directory */
+tree_entry *
+tree_store_whereis (const vfs_path_t * name)
+{
+ tree_entry *current;
+ int flag = -1;
+
+ for (current = ts.tree_first;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ return flag == 0 ? current : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct TreeStore *
+tree_store_get (void)
+{
+ return &ts;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_load(void)
+ * \brief Loads the tree from the default location
+ * \return 1 if success (true), 0 otherwise (false)
+ */
+
+int
+tree_store_load (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ retval = tree_store_load_from (name);
+ g_free (name);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * \fn int tree_store_save(void)
+ * \brief Saves the tree to the default file in an atomic fashion
+ * \return 0 if success, errno on error
+ */
+
+int
+tree_store_save (void)
+{
+ char *name;
+ int retval;
+
+ name = mc_config_get_full_path (MC_TREESTORE_FILE);
+ mc_util_make_backup_if_possible (name, ".tmp");
+
+ retval = tree_store_save_to (name);
+ if (retval != 0)
+ mc_util_restore_from_backup_if_possible (name, ".tmp");
+ else
+ mc_util_unlink_backup_if_possible (name, ".tmp");
+
+ g_free (name);
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data)
+{
+ add_hook (&remove_entry_hooks, (void (*)(void *)) callback, data);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry_remove_hook (tree_store_remove_fn callback)
+{
+ delete_hook (&remove_entry_hooks, (void (*)(void *)) callback);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+tree_store_remove_entry (const vfs_path_t * name_vpath)
+{
+ tree_entry *current, *base;
+ size_t len;
+
+ g_return_if_fail (name_vpath != NULL);
+
+ /* Miguel Ugly hack */
+ {
+ gboolean is_root;
+ const char *name_vpath_str;
+
+ name_vpath_str = vfs_path_as_str (name_vpath);
+ is_root = (IS_PATH_SEP (name_vpath_str[0]) && name_vpath_str[1] == '\0');
+ if (is_root)
+ return;
+ }
+ /* Miguel Ugly hack end */
+
+ base = tree_store_whereis (name_vpath);
+ if (base == NULL)
+ return; /* Doesn't exist */
+
+ len = vfs_path_len (base->name);
+ current = base->next;
+ while (current != NULL && vfs_path_equal_len (current->name, base->name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]));
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ remove_entry (old);
+ }
+ remove_entry (base);
+ tree_store_dirty (TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** This subdirectory exists -> clear deletion mark */
+
+void
+tree_store_mark_checked (const char *subname)
+{
+ vfs_path_t *name;
+ tree_entry *current, *base;
+ int flag = 1;
+ const char *cname;
+
+ if (!ts.loaded)
+ return;
+
+ if (ts.check_name == NULL)
+ return;
+
+ /* Calculate the full name of the subdirectory */
+ if (DIR_IS_DOT (subname) || DIR_IS_DOTDOT (subname))
+ return;
+
+ cname = vfs_path_as_str (ts.check_name);
+ if (IS_PATH_SEP (cname[0]) && cname[1] == '\0')
+ name = vfs_path_build_filename (PATH_SEP_STR, subname, (char *) NULL);
+ else
+ name = vfs_path_append_new (ts.check_name, subname, (char *) NULL);
+
+ /* Search for the subdirectory */
+ for (current = ts.check_start;
+ current != NULL && (flag = pathcmp (current->name, name)) < 0; current = current->next)
+ ;
+
+ if (flag != 0)
+ {
+ /* Doesn't exist -> add it */
+ current = tree_store_add_entry (name);
+ ts.add_queue_vpath = g_list_prepend (ts.add_queue_vpath, name);
+ }
+ else
+ vfs_path_free (name, TRUE);
+
+ /* Clear the deletion mark from the subdirectory and its children */
+ base = current;
+ if (base != NULL)
+ {
+ size_t len;
+
+ len = vfs_path_len (base->name);
+ base->mark = FALSE;
+ for (current = base->next;
+ current != NULL && vfs_path_equal_len (current->name, base->name, len);
+ current = current->next)
+ {
+ gboolean ok;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = FALSE;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Mark the subdirectories of the current directory for delete */
+
+tree_entry *
+tree_store_start_check (const vfs_path_t * vpath)
+{
+ tree_entry *current, *retval;
+ size_t len;
+
+ if (!ts.loaded)
+ return NULL;
+
+ g_return_val_if_fail (ts.check_name == NULL, NULL);
+ ts.check_start = NULL;
+
+ /* Search for the start of subdirectories */
+ current = tree_store_whereis (vpath);
+ if (current == NULL)
+ {
+ struct stat s;
+
+ if (mc_stat (vpath, &s) == -1 || !S_ISDIR (s.st_mode))
+ return NULL;
+
+ current = tree_store_add_entry (vpath);
+ ts.check_name = vfs_path_clone (vpath);
+
+ return current;
+ }
+
+ ts.check_name = vfs_path_clone (vpath);
+
+ retval = current;
+
+ /* Mark old subdirectories for delete */
+ ts.check_start = current->next;
+ len = vfs_path_len (ts.check_name);
+
+ for (current = ts.check_start;
+ current != NULL && vfs_path_equal_len (current->name, ts.check_name, len);
+ current = current->next)
+ {
+ gboolean ok;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ current->mark = TRUE;
+ }
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Delete subdirectories which still have the deletion mark */
+
+void
+tree_store_end_check (void)
+{
+ tree_entry *current;
+ size_t len;
+ GList *the_queue;
+
+ if (!ts.loaded)
+ return;
+
+ g_return_if_fail (ts.check_name != NULL);
+
+ /* Check delete marks and delete if found */
+ len = vfs_path_len (ts.check_name);
+
+ current = ts.check_start;
+ while (current != NULL && vfs_path_equal_len (current->name, ts.check_name, len))
+ {
+ gboolean ok;
+ tree_entry *old;
+ const char *cname;
+
+ cname = vfs_path_as_str (current->name);
+ ok = (cname[len] == '\0' || IS_PATH_SEP (cname[len]) || len == 1);
+ if (!ok)
+ break;
+
+ old = current;
+ current = current->next;
+ if (old->mark)
+ remove_entry (old);
+ }
+
+ /* get the stuff in the scan order */
+ the_queue = g_list_reverse (ts.add_queue_vpath);
+ ts.add_queue_vpath = NULL;
+ vfs_path_free (ts.check_name, TRUE);
+ ts.check_name = NULL;
+
+ g_list_free_full (the_queue, queue_vpath_free);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+tree_entry *
+tree_store_rescan (const vfs_path_t * vpath)
+{
+ DIR *dirp;
+ struct stat buf;
+ tree_entry *entry;
+
+ if (should_skip_directory (vpath))
+ {
+ entry = tree_store_add_entry (vpath);
+ entry->scanned = TRUE;
+ return entry;
+ }
+
+ entry = tree_store_start_check (vpath);
+ if (entry == NULL)
+ return NULL;
+
+ dirp = mc_opendir (vpath);
+ if (dirp != NULL)
+ {
+ struct vfs_dirent *dp;
+
+ for (dp = mc_readdir (dirp); dp != NULL; dp = mc_readdir (dirp))
+ if (!DIR_IS_DOT (dp->d_name) && !DIR_IS_DOTDOT (dp->d_name))
+ {
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_append_new (vpath, dp->d_name, (char *) NULL);
+ if (mc_lstat (tmp_vpath, &buf) != -1 && S_ISDIR (buf.st_mode))
+ tree_store_mark_checked (dp->d_name);
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ mc_closedir (dirp);
+ }
+ tree_store_end_check ();
+ entry->scanned = TRUE;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/src/filemanager/treestore.h b/src/filemanager/treestore.h
new file mode 100644
index 0000000..34e15a9
--- /dev/null
+++ b/src/filemanager/treestore.h
@@ -0,0 +1,63 @@
+/** \file treestore.h
+ * \brief Header: tree store
+ *
+ * Contains a storage of the file system tree representation.
+ */
+
+#ifndef MC__TREE_STORE_H
+#define MC__TREE_STORE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*
+ * Register/unregister notification functions for "entry_remove"
+ */
+struct tree_entry;
+typedef void (*tree_store_remove_fn) (struct tree_entry * tree, void *data);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct tree_entry
+{
+ vfs_path_t *name; /* The full path of directory */
+ int sublevel; /* Number of parent directories (slashes) */
+ long submask; /* Bitmask of existing sublevels after this entry */
+ const char *subname; /* The last part of name (the actual name) */
+ gboolean mark; /* Flag: Is this entry marked (e. g. for delete)? */
+ gboolean scanned; /* Flag: childs scanned or not */
+ struct tree_entry *next; /* Next item in the list */
+ struct tree_entry *prev; /* Previous item in the list */
+} tree_entry;
+
+struct TreeStore
+{
+ tree_entry *tree_first; /* First entry in the list */
+ tree_entry *tree_last; /* Last entry in the list */
+ tree_entry *check_start; /* Start of checked subdirectories */
+ vfs_path_t *check_name;
+ GList *add_queue_vpath; /* List of vfs_path_t objects of added directories */
+ gboolean loaded;
+ gboolean dirty;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+struct TreeStore *tree_store_get (void);
+int tree_store_load (void);
+int tree_store_save (void);
+void tree_store_remove_entry (const vfs_path_t * name_vpath);
+tree_entry *tree_store_start_check (const vfs_path_t * vpath);
+void tree_store_mark_checked (const char *subname);
+void tree_store_end_check (void);
+tree_entry *tree_store_whereis (const vfs_path_t * name);
+tree_entry *tree_store_rescan (const vfs_path_t * vpath);
+
+void tree_store_add_entry_remove_hook (tree_store_remove_fn callback, void *data);
+void tree_store_remove_entry_remove_hook (tree_store_remove_fn callback);
+
+/*** inline functions ****************************************************************************/
+#endif /* MC__TREE_STORE_H */