summaryrefslogtreecommitdiffstats
path: root/lib/widget
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/widget.h53
-rw-r--r--lib/widget/Makefile.am30
-rw-r--r--lib/widget/Makefile.in842
-rw-r--r--lib/widget/background.c124
-rw-r--r--lib/widget/background.h36
-rw-r--r--lib/widget/button.c284
-rw-r--r--lib/widget/button.h58
-rw-r--r--lib/widget/buttonbar.c287
-rw-r--r--lib/widget/buttonbar.h46
-rw-r--r--lib/widget/check.c178
-rw-r--r--lib/widget/check.h33
-rw-r--r--lib/widget/dialog-switch.c368
-rw-r--r--lib/widget/dialog-switch.h37
-rw-r--r--lib/widget/dialog.c662
-rw-r--r--lib/widget/dialog.h129
-rw-r--r--lib/widget/frame.c162
-rw-r--r--lib/widget/frame.h43
-rw-r--r--lib/widget/gauge.c174
-rw-r--r--lib/widget/gauge.h36
-rw-r--r--lib/widget/group.c968
-rw-r--r--lib/widget/group.h125
-rw-r--r--lib/widget/groupbox.c132
-rw-r--r--lib/widget/groupbox.h32
-rw-r--r--lib/widget/history.c298
-rw-r--r--lib/widget/history.h51
-rw-r--r--lib/widget/hline.c192
-rw-r--r--lib/widget/hline.h37
-rw-r--r--lib/widget/input.c1320
-rw-r--r--lib/widget/input.h155
-rw-r--r--lib/widget/input_complete.c1482
-rw-r--r--lib/widget/label.c197
-rw-r--r--lib/widget/label.h37
-rw-r--r--lib/widget/listbox-window.c175
-rw-r--r--lib/widget/listbox-window.h36
-rw-r--r--lib/widget/listbox.c828
-rw-r--r--lib/widget/listbox.h82
-rw-r--r--lib/widget/menu.c1092
-rw-r--r--lib/widget/menu.h63
-rw-r--r--lib/widget/mouse.c225
-rw-r--r--lib/widget/mouse.h65
-rw-r--r--lib/widget/quick.c624
-rw-r--r--lib/widget/quick.h356
-rw-r--r--lib/widget/radio.c249
-rw-r--r--lib/widget/radio.h38
-rw-r--r--lib/widget/rect.c253
-rw-r--r--lib/widget/rect.h45
-rw-r--r--lib/widget/widget-common.c898
-rw-r--r--lib/widget/widget-common.h462
-rw-r--r--lib/widget/wtools.c727
-rw-r--r--lib/widget/wtools.h100
50 files changed, 14926 insertions, 0 deletions
diff --git a/lib/widget.h b/lib/widget.h
new file mode 100644
index 0000000..e3bb5ca
--- /dev/null
+++ b/lib/widget.h
@@ -0,0 +1,53 @@
+/** \file widget.h
+ * \brief Header: MC widget and dialog manager: main include file.
+ */
+#ifndef MC__WIDGET_H
+#define MC__WIDGET_H
+
+#include "lib/global.h" /* GLib */
+
+/* main forward declarations */
+struct Widget;
+typedef struct Widget Widget;
+struct WGroup;
+typedef struct WGroup WGroup;
+
+/* Please note that the first element in all the widgets is a */
+/* widget variable of type Widget. We abuse this fact everywhere */
+
+#include "lib/widget/rect.h"
+#include "lib/widget/widget-common.h"
+#include "lib/widget/group.h"
+#include "lib/widget/background.h"
+#include "lib/widget/frame.h"
+#include "lib/widget/dialog.h"
+#include "lib/widget/history.h"
+#include "lib/widget/button.h"
+#include "lib/widget/buttonbar.h"
+#include "lib/widget/check.h"
+#include "lib/widget/hline.h"
+#include "lib/widget/gauge.h"
+#include "lib/widget/groupbox.h"
+#include "lib/widget/label.h"
+#include "lib/widget/listbox.h"
+#include "lib/widget/menu.h"
+#include "lib/widget/radio.h"
+#include "lib/widget/input.h"
+#include "lib/widget/listbox-window.h"
+#include "lib/widget/quick.h"
+#include "lib/widget/wtools.h"
+#include "lib/widget/dialog-switch.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_H */
diff --git a/lib/widget/Makefile.am b/lib/widget/Makefile.am
new file mode 100644
index 0000000..90f023b
--- /dev/null
+++ b/lib/widget/Makefile.am
@@ -0,0 +1,30 @@
+
+noinst_LTLIBRARIES = libmcwidget.la
+
+libmcwidget_la_SOURCES = \
+ background.c background.h \
+ button.c button.h \
+ buttonbar.c buttonbar.h \
+ check.c check.h \
+ dialog.c dialog.h \
+ dialog-switch.c dialog-switch.h \
+ frame.c frame.h \
+ gauge.c gauge.h \
+ group.c group.h \
+ groupbox.c groupbox.h \
+ hline.c hline.h \
+ history.c history.h \
+ input.c input.h \
+ input_complete.c \
+ listbox-window.c listbox-window.h \
+ listbox.c listbox.h \
+ label.c label.h \
+ menu.c menu.h \
+ mouse.c mouse.h \
+ quick.c quick.h \
+ radio.c radio.h \
+ rect.c rect.h \
+ widget-common.c widget-common.h \
+ wtools.c wtools.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
diff --git a/lib/widget/Makefile.in b/lib/widget/Makefile.in
new file mode 100644
index 0000000..1fc9d1d
--- /dev/null
+++ b/lib/widget/Makefile.in
@@ -0,0 +1,842 @@
+# 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@
+subdir = lib/widget
+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/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)
+libmcwidget_la_LIBADD =
+am_libmcwidget_la_OBJECTS = background.lo button.lo buttonbar.lo \
+ check.lo dialog.lo dialog-switch.lo frame.lo gauge.lo group.lo \
+ groupbox.lo hline.lo history.lo input.lo input_complete.lo \
+ listbox-window.lo listbox.lo label.lo menu.lo mouse.lo \
+ quick.lo radio.lo rect.lo widget-common.lo wtools.lo
+libmcwidget_la_OBJECTS = $(am_libmcwidget_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)/background.Plo \
+ ./$(DEPDIR)/button.Plo ./$(DEPDIR)/buttonbar.Plo \
+ ./$(DEPDIR)/check.Plo ./$(DEPDIR)/dialog-switch.Plo \
+ ./$(DEPDIR)/dialog.Plo ./$(DEPDIR)/frame.Plo \
+ ./$(DEPDIR)/gauge.Plo ./$(DEPDIR)/group.Plo \
+ ./$(DEPDIR)/groupbox.Plo ./$(DEPDIR)/history.Plo \
+ ./$(DEPDIR)/hline.Plo ./$(DEPDIR)/input.Plo \
+ ./$(DEPDIR)/input_complete.Plo ./$(DEPDIR)/label.Plo \
+ ./$(DEPDIR)/listbox-window.Plo ./$(DEPDIR)/listbox.Plo \
+ ./$(DEPDIR)/menu.Plo ./$(DEPDIR)/mouse.Plo \
+ ./$(DEPDIR)/quick.Plo ./$(DEPDIR)/radio.Plo \
+ ./$(DEPDIR)/rect.Plo ./$(DEPDIR)/widget-common.Plo \
+ ./$(DEPDIR)/wtools.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 = $(libmcwidget_la_SOURCES)
+DIST_SOURCES = $(libmcwidget_la_SOURCES)
+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_CPPFLAGS = @PCRE_CPPFLAGS@
+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 = libmcwidget.la
+libmcwidget_la_SOURCES = \
+ background.c background.h \
+ button.c button.h \
+ buttonbar.c buttonbar.h \
+ check.c check.h \
+ dialog.c dialog.h \
+ dialog-switch.c dialog-switch.h \
+ frame.c frame.h \
+ gauge.c gauge.h \
+ group.c group.h \
+ groupbox.c groupbox.h \
+ hline.c hline.h \
+ history.c history.h \
+ input.c input.h \
+ input_complete.c \
+ listbox-window.c listbox-window.h \
+ listbox.c listbox.h \
+ label.c label.h \
+ menu.c menu.h \
+ mouse.c mouse.h \
+ quick.c quick.h \
+ radio.c radio.h \
+ rect.c rect.h \
+ widget-common.c widget-common.h \
+ wtools.c wtools.h
+
+AM_CPPFLAGS = -I$(top_srcdir) $(GLIB_CFLAGS)
+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 lib/widget/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu lib/widget/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}; \
+ }
+
+libmcwidget.la: $(libmcwidget_la_OBJECTS) $(libmcwidget_la_DEPENDENCIES) $(EXTRA_libmcwidget_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libmcwidget_la_OBJECTS) $(libmcwidget_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/background.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/button.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buttonbar.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/check.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialog-switch.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dialog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/frame.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gauge.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/group.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/groupbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/history.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input_complete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/label.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listbox-window.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/listbox.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/menu.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mouse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quick.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/radio.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/widget-common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/wtools.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)/background.Plo
+ -rm -f ./$(DEPDIR)/button.Plo
+ -rm -f ./$(DEPDIR)/buttonbar.Plo
+ -rm -f ./$(DEPDIR)/check.Plo
+ -rm -f ./$(DEPDIR)/dialog-switch.Plo
+ -rm -f ./$(DEPDIR)/dialog.Plo
+ -rm -f ./$(DEPDIR)/frame.Plo
+ -rm -f ./$(DEPDIR)/gauge.Plo
+ -rm -f ./$(DEPDIR)/group.Plo
+ -rm -f ./$(DEPDIR)/groupbox.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/hline.Plo
+ -rm -f ./$(DEPDIR)/input.Plo
+ -rm -f ./$(DEPDIR)/input_complete.Plo
+ -rm -f ./$(DEPDIR)/label.Plo
+ -rm -f ./$(DEPDIR)/listbox-window.Plo
+ -rm -f ./$(DEPDIR)/listbox.Plo
+ -rm -f ./$(DEPDIR)/menu.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/quick.Plo
+ -rm -f ./$(DEPDIR)/radio.Plo
+ -rm -f ./$(DEPDIR)/rect.Plo
+ -rm -f ./$(DEPDIR)/widget-common.Plo
+ -rm -f ./$(DEPDIR)/wtools.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)/background.Plo
+ -rm -f ./$(DEPDIR)/button.Plo
+ -rm -f ./$(DEPDIR)/buttonbar.Plo
+ -rm -f ./$(DEPDIR)/check.Plo
+ -rm -f ./$(DEPDIR)/dialog-switch.Plo
+ -rm -f ./$(DEPDIR)/dialog.Plo
+ -rm -f ./$(DEPDIR)/frame.Plo
+ -rm -f ./$(DEPDIR)/gauge.Plo
+ -rm -f ./$(DEPDIR)/group.Plo
+ -rm -f ./$(DEPDIR)/groupbox.Plo
+ -rm -f ./$(DEPDIR)/history.Plo
+ -rm -f ./$(DEPDIR)/hline.Plo
+ -rm -f ./$(DEPDIR)/input.Plo
+ -rm -f ./$(DEPDIR)/input_complete.Plo
+ -rm -f ./$(DEPDIR)/label.Plo
+ -rm -f ./$(DEPDIR)/listbox-window.Plo
+ -rm -f ./$(DEPDIR)/listbox.Plo
+ -rm -f ./$(DEPDIR)/menu.Plo
+ -rm -f ./$(DEPDIR)/mouse.Plo
+ -rm -f ./$(DEPDIR)/quick.Plo
+ -rm -f ./$(DEPDIR)/radio.Plo
+ -rm -f ./$(DEPDIR)/rect.Plo
+ -rm -f ./$(DEPDIR)/widget-common.Plo
+ -rm -f ./$(DEPDIR)/wtools.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/lib/widget/background.c b/lib/widget/background.c
new file mode 100644
index 0000000..f085312
--- /dev/null
+++ b/lib/widget/background.c
@@ -0,0 +1,124 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2020-2022
+ The Free Software Foundation, Inc.
+
+ Authors:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-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 background.c
+ * \brief Source: WBackground widget (background area of dialog)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+background_get_colors (const Widget * w)
+{
+ return &(CONST_BACKGROUND (w)->color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+background_adjust (WBackground * b)
+{
+ Widget *w = WIDGET (b);
+
+ w->rect = WIDGET (w->owner)->rect;
+ w->pos_flags |= WPOS_KEEP_ALL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+background_draw (const WBackground * b)
+{
+ const Widget *w = CONST_WIDGET (b);
+
+ tty_setcolor (b->color);
+ tty_fill_region (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, b->pattern);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+background_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WBackground *b = BACKGROUND (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ background_adjust (b);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ background_draw (b);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WBackground *
+background_new (int y, int x, int lines, int cols, int color, unsigned char pattern,
+ widget_cb_fn callback)
+{
+ WRect r = { y, x, lines, cols };
+ WBackground *b;
+ Widget *w;
+
+ b = g_new (WBackground, 1);
+ w = WIDGET (b);
+ widget_init (w, &r, callback != NULL ? callback : background_callback, NULL);
+ w->get_colors = background_get_colors;
+
+ b->color = color;
+ b->pattern = pattern;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/background.h b/lib/widget/background.h
new file mode 100644
index 0000000..b9a0b2c
--- /dev/null
+++ b/lib/widget/background.h
@@ -0,0 +1,36 @@
+
+/** \file background.h
+ * \brief Header: WBackground widget
+ */
+
+#ifndef MC__WIDGET_BACKGROUND_H
+#define MC__WIDGET_BACKGROUND_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BACKGROUND(x) ((WBackground *)(x))
+#define CONST_BACKGROUND(x) ((const WBackground *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+
+ int color; /* Color to fill area */
+ unsigned char pattern; /* Symbol to fill area */
+} WBackground;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WBackground *background_new (int y, int x, int lines, int cols, int color, unsigned char pattern,
+ widget_cb_fn callback);
+cb_ret_t background_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BACKGROUND_H */
diff --git a/lib/widget/button.c b/lib/widget/button.c
new file mode 100644
index 0000000..da7acfb
--- /dev/null
+++ b/lib/widget/button.c
@@ -0,0 +1,284 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 button.c
+ * \brief Source: WButton widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+button_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButton *b = BUTTON (w);
+ WGroup *g = w->owner;
+ WDialog *h = DIALOG (g);
+ int off = 0;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ /*
+ * Don't let the default button steal Enter from the current
+ * button. This is a workaround for the flawed event model
+ * when hotkeys are sent to all widgets before the key is
+ * handled by the current widget.
+ */
+ if (parm == '\n' && WIDGET (g->current->data) == w)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+
+ if (parm == '\n' && b->flags == DEFPUSH_BUTTON)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+
+ if (b->text.hotkey != NULL && g_ascii_tolower ((gchar) b->text.hotkey[0]) == parm)
+ {
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm != ' ' && parm != '\n')
+ return MSG_NOT_HANDLED;
+
+ h->ret_value = b->action;
+ if (b->callback == NULL || b->callback (b, b->action) != 0)
+ dlg_stop (h);
+
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ off = 3;
+ break;
+ case NORMAL_BUTTON:
+ off = 2;
+ break;
+ case NARROW_BUTTON:
+ off = 1;
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ off = 0;
+ break;
+ }
+ widget_gotoyx (w, 0, b->hotpos + off);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+
+ widget_selectcolor (w, focused, FALSE);
+ widget_gotoyx (w, 0, 0);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ tty_print_string ("[< ");
+ break;
+ case NORMAL_BUTTON:
+ tty_print_string ("[ ");
+ break;
+ case NARROW_BUTTON:
+ tty_print_string ("[");
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ return MSG_HANDLED;
+ }
+
+ hotkey_draw (w, b->text, focused);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ tty_print_string (" >]");
+ break;
+ case NORMAL_BUTTON:
+ tty_print_string (" ]");
+ break;
+ case NARROW_BUTTON:
+ tty_print_string ("]");
+ break;
+ default:
+ break;
+ }
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ hotkey_free (b->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+button_mouse_default_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ send_message (w, NULL, MSG_KEY, ' ', NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WButton *
+button_new (int y, int x, int action, button_flags_t flags, const char *text, bcback_fn callback)
+{
+ WRect r = { y, x, 1, 1 };
+ WButton *b;
+ Widget *w;
+
+ b = g_new (WButton, 1);
+ w = WIDGET (b);
+
+ b->action = action;
+ b->flags = flags;
+ b->text = hotkey_new (text);
+ r.cols = button_get_len (b);
+ widget_init (w, &r, button_default_callback, button_mouse_default_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ b->callback = callback;
+ b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
+
+ return b;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+button_get_text (const WButton * b)
+{
+ return hotkey_get_text (b->text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+button_set_text (WButton * b, const char *text)
+{
+ Widget *w = WIDGET (b);
+ hotkey_t hk;
+
+ hk = hotkey_new (text);
+ if (hotkey_equal (b->text, hk))
+ {
+ hotkey_free (hk);
+ return;
+ }
+
+ hotkey_free (b->text);
+ b->text = hk;
+ b->hotpos = (b->text.hotkey != NULL) ? str_term_width1 (b->text.start) : -1;
+ w->rect.cols = button_get_len (b);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+button_get_len (const WButton * b)
+{
+ int ret = hotkey_width (b->text);
+
+ switch (b->flags)
+ {
+ case DEFPUSH_BUTTON:
+ ret += 6;
+ break;
+ case NORMAL_BUTTON:
+ ret += 4;
+ break;
+ case NARROW_BUTTON:
+ ret += 2;
+ break;
+ case HIDDEN_BUTTON:
+ default:
+ return 0;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/button.h b/lib/widget/button.h
new file mode 100644
index 0000000..5f21e1e
--- /dev/null
+++ b/lib/widget/button.h
@@ -0,0 +1,58 @@
+
+/** \file button.h
+ * \brief Header: WButton widget
+ */
+
+#ifndef MC__WIDGET_BUTTON_H
+#define MC__WIDGET_BUTTON_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BUTTON(x) ((WButton *)(x))
+
+struct WButton;
+
+/* button callback */
+/* return 0 to continue work with dialog, non-zero to close */
+typedef int (*bcback_fn) (struct WButton * button, int action);
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ HIDDEN_BUTTON = 0,
+ NARROW_BUTTON = 1,
+ NORMAL_BUTTON = 2,
+ DEFPUSH_BUTTON = 3
+} button_flags_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WButton
+{
+ Widget widget;
+ int action; /* what to do when pressed */
+
+ button_flags_t flags; /* button flags */
+ hotkey_t text; /* text of button, contain hotkey too */
+ int hotpos; /* offset hot KEY char in text */
+ bcback_fn callback; /* callback function */
+} WButton;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WButton *button_new (int y, int x, int action, button_flags_t flags, const char *text,
+ bcback_fn callback);
+char *button_get_text (const WButton * b);
+void button_set_text (WButton * b, const char *text);
+int button_get_len (const WButton * b);
+
+cb_ret_t button_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+void button_mouse_default_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BUTTON_H */
diff --git a/lib/widget/buttonbar.c b/lib/widget/buttonbar.c
new file mode 100644
index 0000000..9193de3
--- /dev/null
+++ b/lib/widget/buttonbar.c
@@ -0,0 +1,287 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 buttonbar.c
+ * \brief Source: WButtonBar widget
+ */
+
+#include <config.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/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* calculate positions of buttons; width is never less than 7 */
+static void
+buttonbar_init_button_positions (WButtonBar * bb)
+{
+ int i;
+ int pos = 0;
+
+ if (COLS < BUTTONBAR_LABELS_NUM * 7)
+ {
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ if (pos + 7 <= COLS)
+ pos += 7;
+
+ bb->labels[i].end_coord = pos;
+ }
+ }
+ else
+ {
+ /* Distribute the extra width in a way that the middle vertical line
+ (between F5 and F6) aligns with the two panels. The extra width
+ is distributed in this order: F10, F5, F9, F4, ..., F6, F1. */
+ int dv, md;
+
+ dv = COLS / BUTTONBAR_LABELS_NUM;
+ md = COLS % BUTTONBAR_LABELS_NUM;
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM / 2; i++)
+ {
+ pos += dv;
+ if (BUTTONBAR_LABELS_NUM / 2 - 1 - i < md / 2)
+ pos++;
+
+ bb->labels[i].end_coord = pos;
+ }
+
+ for (; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ pos += dv;
+ if (BUTTONBAR_LABELS_NUM - 1 - i < (md + 1) / 2)
+ pos++;
+
+ bb->labels[i].end_coord = pos;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* return width of one button */
+static int
+buttonbar_get_button_width (const WButtonBar * bb, int i)
+{
+ if (i == 0)
+ return bb->labels[0].end_coord;
+ return bb->labels[i].end_coord - bb->labels[i - 1].end_coord;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+buttonbar_get_button_by_x_coord (const WButtonBar * bb, int x)
+{
+ int i;
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ if (bb->labels[i].end_coord > x)
+ return i;
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+set_label_text (WButtonBar * bb, int idx, const char *text)
+{
+ g_free (bb->labels[idx - 1].text);
+ bb->labels[idx - 1].text = g_strdup (text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* returns TRUE if a function has been called, FALSE otherwise. */
+static gboolean
+buttonbar_call (WButtonBar * bb, int i)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+ Widget *w = WIDGET (bb);
+ Widget *target;
+
+ if ((bb != NULL) && (bb->labels[i].command != CK_IgnoreKey))
+ {
+ target = (bb->labels[i].receiver != NULL) ? bb->labels[i].receiver : WIDGET (w->owner);
+ ret = send_message (target, w, MSG_ACTION, bb->labels[i].command, NULL);
+ }
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+buttonbar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WButtonBar *bb = BUTTONBAR (w);
+ int i;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ if (parm == KEY_F (i + 1) && buttonbar_call (bb, i))
+ return MSG_HANDLED;
+ return MSG_NOT_HANDLED;
+
+ case MSG_DRAW:
+ if (widget_get_state (w, WST_VISIBLE))
+ {
+ buttonbar_init_button_positions (bb);
+ widget_gotoyx (w, 0, 0);
+ tty_setcolor (DEFAULT_COLOR);
+ tty_printf ("%-*s", w->rect.cols, "");
+ widget_gotoyx (w, 0, 0);
+
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ {
+ int width;
+ const char *text;
+
+ width = buttonbar_get_button_width (bb, i);
+ if (width <= 0)
+ break;
+
+ tty_setcolor (BUTTONBAR_HOTKEY_COLOR);
+ tty_printf ("%2d", i + 1);
+
+ tty_setcolor (BUTTONBAR_BUTTON_COLOR);
+ text = (bb->labels[i].text != NULL) ? bb->labels[i].text : "";
+ tty_print_string (str_fit_to_term (text, width - 2, J_LEFT_FIT));
+ }
+ }
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ for (i = 0; i < BUTTONBAR_LABELS_NUM; i++)
+ g_free (bb->labels[i].text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+buttonbar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_CLICK:
+ {
+ WButtonBar *bb = BUTTONBAR (w);
+ int button;
+
+ button = buttonbar_get_button_by_x_coord (bb, event->x);
+ if (button >= 0)
+ buttonbar_call (bb, button);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WButtonBar *
+buttonbar_new (void)
+{
+ WRect r = { LINES - 1, 0, 1, COLS };
+ WButtonBar *bb;
+ Widget *w;
+
+ bb = g_new0 (WButtonBar, 1);
+ w = WIDGET (bb);
+ widget_init (w, &r, buttonbar_callback, buttonbar_mouse_callback);
+
+ w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_BOTTOM;
+ widget_want_hotkey (w, TRUE);
+
+ return bb;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+buttonbar_set_label (WButtonBar * bb, int idx, const char *text, const global_keymap_t * keymap,
+ Widget * receiver)
+{
+ if ((bb != NULL) && (idx >= 1) && (idx <= BUTTONBAR_LABELS_NUM))
+ {
+ long command = CK_IgnoreKey;
+
+ if (keymap != NULL)
+ command = keybind_lookup_keymap_command (keymap, KEY_F (idx));
+
+ if ((text == NULL) || (text[0] == '\0'))
+ set_label_text (bb, idx, "");
+ else
+ set_label_text (bb, idx, text);
+
+ bb->labels[idx - 1].command = command;
+ bb->labels[idx - 1].receiver = WIDGET (receiver);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Find ButtonBar widget in the dialog */
+WButtonBar *
+find_buttonbar (const WDialog * h)
+{
+ return BUTTONBAR (widget_find_by_type (CONST_WIDGET (h), buttonbar_callback));
+}
diff --git a/lib/widget/buttonbar.h b/lib/widget/buttonbar.h
new file mode 100644
index 0000000..9fc049a
--- /dev/null
+++ b/lib/widget/buttonbar.h
@@ -0,0 +1,46 @@
+
+/** \file buttonbar.h
+ * \brief Header: WButtonBar widget
+ */
+
+#ifndef MC__WIDGET_BUTTONBAR_H
+#define MC__WIDGET_BUTTONBAR_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define BUTTONBAR(x) ((WButtonBar *)(x))
+
+/* number of bttons in buttonbar */
+#define BUTTONBAR_LABELS_NUM 10
+
+#define buttonbar_clear_label(bb, idx, recv) buttonbar_set_label (bb, idx, "", NULL, recv)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WButtonBar
+{
+ Widget widget;
+
+ struct
+ {
+ char *text;
+ long command;
+ Widget *receiver;
+ int end_coord; /* cumulative width of buttons so far */
+ } labels[BUTTONBAR_LABELS_NUM];
+} WButtonBar;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WButtonBar *buttonbar_new (void);
+void buttonbar_set_label (WButtonBar * bb, int idx, const char *text,
+ const global_keymap_t * keymap, Widget * receiver);
+WButtonBar *find_buttonbar (const WDialog * h);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_BUTTONBAR_H */
diff --git a/lib/widget/check.c b/lib/widget/check.c
new file mode 100644
index 0000000..ca54e3b
--- /dev/null
+++ b/lib/widget/check.c
@@ -0,0 +1,178 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 check.c
+ * \brief Source: WCheck widget (checkbutton)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static cb_ret_t
+check_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WCheck *c = CHECK (w);
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ if (c->text.hotkey != NULL)
+ {
+ if (g_ascii_tolower ((gchar) c->text.hotkey[0]) == parm)
+ {
+ /* make action */
+ send_message (w, sender, MSG_KEY, ' ', data);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ if (parm != ' ')
+ return MSG_NOT_HANDLED;
+ c->state = !c->state;
+ widget_draw (w);
+ send_message (w->owner, w, MSG_NOTIFY, 0, NULL);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ widget_gotoyx (w, 0, 1);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+ widget_selectcolor (w, focused, FALSE);
+ widget_gotoyx (w, 0, 0);
+ tty_print_string (c->state ? "[x] " : "[ ] ");
+ hotkey_draw (w, c->text, focused);
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ hotkey_free (c->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+check_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ (void) event;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ send_message (w, NULL, MSG_KEY, ' ', NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WCheck *
+check_new (int y, int x, gboolean state, const char *text)
+{
+ WRect r = { y, x, 1, 1 };
+ WCheck *c;
+ Widget *w;
+
+ c = g_new (WCheck, 1);
+ w = WIDGET (c);
+ c->text = hotkey_new (text);
+ /* 4 is width of "[X] " */
+ r.cols = 4 + hotkey_width (c->text);
+ widget_init (w, &r, check_callback, check_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ c->state = state;
+
+ return c;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+check_set_text (WCheck * check, const char *text)
+{
+ Widget *w = WIDGET (check);
+ hotkey_t hk;
+
+ hk = hotkey_new (text);
+ if (hotkey_equal (check->text, hk))
+ {
+ hotkey_free (hk);
+ return;
+ }
+
+ hotkey_free (check->text);
+ check->text = hk;
+
+ if (check->text.start[0] == '\0' && check->text.hotkey == NULL && check->text.end == NULL)
+ w->rect.cols = 3; /* "[ ]" */
+ else
+ w->rect.cols = 4 + hotkey_width (check->text); /* "[ ] text" */
+
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/check.h b/lib/widget/check.h
new file mode 100644
index 0000000..9840a24
--- /dev/null
+++ b/lib/widget/check.h
@@ -0,0 +1,33 @@
+
+/** \file check.h
+ * \brief Header: WCheck widget
+ */
+
+#ifndef MC__WIDGET_CHECK_H
+#define MC__WIDGET_CHECK_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define CHECK(x) ((WCheck *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WCheck
+{
+ Widget widget;
+ gboolean state; /* check button state */
+ hotkey_t text; /* text of check button */
+} WCheck;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WCheck *check_new (int y, int x, gboolean state, const char *text);
+void check_set_text (WCheck * check, const char *text);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_CHECK_H */
diff --git a/lib/widget/dialog-switch.c b/lib/widget/dialog-switch.c
new file mode 100644
index 0000000..73e3f80
--- /dev/null
+++ b/lib/widget/dialog-switch.c
@@ -0,0 +1,368 @@
+/*
+ Support of multiply editors and viewers.
+
+ Original idea and code: Oleg "Olegarch" Konovalov <olegarch@linuxinside.com>
+
+ Copyright (C) 2009-2022
+ Free Software Foundation, Inc.
+
+ Written by:
+ Daniel Borca <dborca@yahoo.com>, 2007
+ Andrew Borodin <aborodin@vmail.ru>, 2010-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 dialog-switch.c
+ * \brief Source: support of multiply editors and viewers.
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/tty/color.h" /* tty_set_normal_attrs() */
+#include "lib/widget.h"
+#include "lib/event.h"
+
+/*** global variables ****************************************************************************/
+
+WDialog *filemanager = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* List of dialogs: filemanagers, editors, viewers */
+static GList *mc_dialogs = NULL;
+/* Currently active dialog */
+static GList *mc_current = NULL;
+/* Is there any dialogs that we have to run after returning to the manager from another dialog */
+static gboolean dialog_switch_pending = FALSE;
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned char
+get_hotkey (int n)
+{
+ return (n <= 9) ? '0' + n : 'a' + n - 10;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_suspend (void *data, void *user_data)
+{
+ (void) user_data;
+
+ if (data != mc_current->data)
+ widget_set_state (WIDGET (data), WST_SUSPENDED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_goto (GList * dlg)
+{
+ if (mc_current != dlg)
+ {
+ WDialog *old = DIALOG (mc_current->data);
+
+ mc_current = dlg;
+
+ if (old == filemanager)
+ {
+ /* switch from panels to another dialog (editor, viewer, etc) */
+ dialog_switch_pending = TRUE;
+ dialog_switch_process_pending ();
+ }
+ else
+ {
+ /* switch from editor, viewer, etc to another dialog */
+ widget_set_state (WIDGET (old), WST_SUSPENDED, TRUE);
+
+ if (DIALOG (dlg->data) != filemanager)
+ /* switch to another editor, viewer, etc */
+ /* return to panels before run the required dialog */
+ dialog_switch_pending = TRUE;
+ else
+ {
+ /* switch to panels */
+ widget_set_state (WIDGET (filemanager), WST_ACTIVE, TRUE);
+ do_refresh ();
+ }
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dialog_switch_resize (WDialog * d)
+{
+ if (widget_get_state (WIDGET (d), WST_ACTIVE))
+ send_message (d, NULL, MSG_RESIZE, 0, NULL);
+ else
+ GROUP (d)->winch_pending = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_add (WDialog * h)
+{
+ GList *dlg;
+
+ dlg = g_list_find (mc_dialogs, h);
+
+ if (dlg != NULL)
+ mc_current = dlg;
+ else
+ {
+ mc_dialogs = g_list_prepend (mc_dialogs, h);
+ mc_current = mc_dialogs;
+ }
+
+ /* suspend forced all other screens */
+ g_list_foreach (mc_dialogs, dialog_switch_suspend, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_remove (WDialog * h)
+{
+ GList *this;
+
+ if (DIALOG (mc_current->data) == h)
+ this = mc_current;
+ else
+ this = g_list_find (mc_dialogs, h);
+
+ mc_dialogs = g_list_delete_link (mc_dialogs, this);
+
+ /* adjust current dialog */
+ if (top_dlg != NULL)
+ mc_current = g_list_find (mc_dialogs, DIALOG (top_dlg->data));
+ else
+ mc_current = mc_dialogs;
+
+ /* resume forced the current screen */
+ if (mc_current != NULL)
+ widget_set_state (WIDGET (mc_current->data), WST_ACTIVE, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+size_t
+dialog_switch_num (void)
+{
+ return g_list_length (mc_dialogs);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_next (void)
+{
+ GList *next;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ next = g_list_next (mc_current);
+ if (next == NULL)
+ next = mc_dialogs;
+
+ dialog_switch_goto (next);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_prev (void)
+{
+ GList *prev;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ prev = g_list_previous (mc_current);
+ if (prev == NULL)
+ prev = g_list_last (mc_dialogs);
+
+ dialog_switch_goto (prev);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_list (void)
+{
+ const size_t dlg_num = g_list_length (mc_dialogs);
+ int lines, cols;
+ Listbox *listbox;
+ GList *h, *selected;
+ int i = 0;
+
+ if (mc_global.midnight_shutdown || mc_current == NULL)
+ return;
+
+ lines = MIN ((size_t) (LINES * 2 / 3), dlg_num);
+ cols = COLS * 2 / 3;
+
+ listbox = create_listbox_window (lines, cols, _("Screens"), "[Screen selector]");
+
+ for (h = mc_dialogs; h != NULL; h = g_list_next (h))
+ {
+ WDialog *dlg = DIALOG (h->data);
+ char *title;
+
+ if (dlg->get_title != NULL)
+ title = dlg->get_title (dlg, WIDGET (listbox->list)->rect.cols - 2);
+ else
+ title = g_strdup ("");
+
+ listbox_add_item (listbox->list, LISTBOX_APPEND_BEFORE, get_hotkey (i++), title, h, FALSE);
+
+ g_free (title);
+ }
+
+ selected = run_listbox_with_data (listbox, mc_current);
+ if (selected != NULL)
+ dialog_switch_goto (selected);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+dialog_switch_process_pending (void)
+{
+ int ret = 0;
+
+ while (dialog_switch_pending)
+ {
+ WDialog *h = DIALOG (mc_current->data);
+ Widget *wh = WIDGET (h);
+
+ dialog_switch_pending = FALSE;
+ widget_set_state (wh, WST_SUSPENDED, TRUE);
+ ret = dlg_run (h);
+ if (widget_get_state (wh, WST_CLOSED))
+ {
+ widget_destroy (wh);
+
+ /* return to panels */
+ if (mc_global.mc_run_mode == MC_RUN_FULL)
+ {
+ mc_current = g_list_find (mc_dialogs, filemanager);
+ mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "update_panels", NULL);
+ }
+ }
+ }
+
+ repaint_screen ();
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_got_winch (void)
+{
+ GList *dlg;
+
+ for (dlg = mc_dialogs; dlg != NULL; dlg = g_list_next (dlg))
+ if (dlg != mc_current)
+ GROUP (dlg->data)->winch_pending = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_switch_shutdown (void)
+{
+ while (mc_dialogs != NULL)
+ {
+ WDialog *dlg = DIALOG (mc_dialogs->data);
+
+ dlg_run (dlg);
+ widget_destroy (WIDGET (dlg));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+repaint_screen (void)
+{
+ do_refresh ();
+ tty_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+mc_refresh (void)
+{
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ return;
+#endif /* ENABLE_BACKGROUND */
+
+ if (!tty_got_winch ())
+ tty_refresh ();
+ else
+ {
+ /* if winch was caugth, we should do not only redraw screen, but
+ reposition/resize all */
+ dialog_change_screen_size ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dialog_change_screen_size (void)
+{
+ GList *d;
+
+ tty_flush_winch ();
+ tty_change_screen_size ();
+
+#ifdef HAVE_SLANG
+ tty_keypad (TRUE);
+ tty_nodelay (FALSE);
+#endif
+
+ /* Inform all suspending dialogs */
+ dialog_switch_got_winch ();
+
+ /* Inform all running dialogs from first to last */
+ for (d = g_list_last (top_dlg); d != NULL; d = g_list_previous (d))
+ dialog_switch_resize (DIALOG (d->data));
+
+ /* Now, force the redraw */
+ repaint_screen ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/dialog-switch.h b/lib/widget/dialog-switch.h
new file mode 100644
index 0000000..332b71d
--- /dev/null
+++ b/lib/widget/dialog-switch.h
@@ -0,0 +1,37 @@
+
+#ifndef MC__DIALOG_SWITCH_H
+#define MC__DIALOG_SWITCH_H
+
+#include <sys/types.h>
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+extern WDialog *filemanager;
+
+/*** declarations of public functions ************************************************************/
+
+void dialog_switch_add (WDialog * h);
+void dialog_switch_remove (WDialog * h);
+size_t dialog_switch_num (void);
+
+void dialog_switch_next (void);
+void dialog_switch_prev (void);
+void dialog_switch_list (void);
+
+int dialog_switch_process_pending (void);
+void dialog_switch_got_winch (void);
+void dialog_switch_shutdown (void);
+
+void repaint_screen (void);
+void mc_refresh (void);
+void dialog_change_screen_size (void);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__DIALOG_SWITCH_H */
diff --git a/lib/widget/dialog.c b/lib/widget/dialog.c
new file mode 100644
index 0000000..1c93d7c
--- /dev/null
+++ b/lib/widget/dialog.c
@@ -0,0 +1,662 @@
+/*
+ Dialog box features module for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ 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 dialog.c
+ * \brief Source: dialog box features module
+ */
+
+#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 "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h"
+#include "lib/strutil.h"
+#include "lib/fileloc.h" /* MC_HISTORY_FILE */
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/util.h" /* MC_PTR_FREE */
+#include "lib/mcconfig.h" /* num_history_items_recorded */
+
+#include "lib/widget.h"
+#include "lib/widget/mouse.h"
+
+/*** global variables ****************************************************************************/
+
+/* Color styles for normal and error dialogs */
+dlg_colors_t dialog_colors;
+dlg_colors_t alarm_colors;
+dlg_colors_t listbox_colors;
+
+/* Primitive way to check if the the current dialog is our dialog */
+/* This is needed by async routines like load_prompt */
+GList *top_dlg = NULL;
+
+/* A hook list for idle events */
+hook_t *idle_hook = NULL;
+
+/* If set then dialogs just clean the screen when refreshing, else */
+/* they do a complete refresh, refreshing all the parts of the program */
+gboolean fast_refresh = FALSE;
+
+/* left click outside of dialog closes it */
+gboolean mouse_close_dialog = FALSE;
+
+const global_keymap_t *dialog_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+dlg_default_get_colors (const Widget * w)
+{
+ return CONST_DIALOG (w)->colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Read histories from the ${XDG_CACHE_HOME}/mc/history file
+ */
+static void
+dlg_read_history (WDialog * h)
+{
+ char *profile;
+ ev_history_load_save_t event_data;
+
+ if (num_history_items_recorded == 0) /* this is how to disable */
+ return;
+
+ profile = mc_config_get_full_path (MC_HISTORY_FILE);
+ event_data.cfg = mc_config_init (profile, TRUE);
+ event_data.receiver = NULL;
+
+ /* create all histories in dialog */
+ mc_event_raise (h->event_group, MCEVENT_HISTORY_LOAD, &event_data);
+
+ mc_config_deinit (event_data.cfg);
+ g_free (profile);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+refresh_cmd (void)
+{
+#ifdef HAVE_SLANG
+ tty_touch_screen ();
+ mc_refresh ();
+#else
+ /* Use this if the refreshes fail */
+ tty_clear_screen ();
+ repaint_screen ();
+#endif /* HAVE_SLANG */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dlg_execute_cmd (WDialog * h, long command)
+{
+ WGroup *g = GROUP (h);
+ cb_ret_t ret = MSG_HANDLED;
+
+ if (send_message (h, NULL, MSG_ACTION, command, NULL) == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Ok:
+ h->ret_value = B_ENTER;
+ dlg_stop (h);
+ break;
+ case CK_Cancel:
+ h->ret_value = B_CANCEL;
+ dlg_stop (h);
+ break;
+
+ case CK_Up:
+ case CK_Left:
+ group_select_prev_widget (g);
+ break;
+ case CK_Down:
+ case CK_Right:
+ group_select_next_widget (g);
+ break;
+
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, h->help_ctx };
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ }
+ break;
+
+ case CK_Suspend:
+ mc_event_raise (MCEVENT_GROUP_CORE, "suspend", NULL);
+ refresh_cmd ();
+ break;
+ case CK_Refresh:
+ refresh_cmd ();
+ break;
+
+ case CK_ScreenList:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_list ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+ case CK_ScreenNext:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_next ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+ case CK_ScreenPrev:
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_prev ();
+ else
+ ret = MSG_NOT_HANDLED;
+ break;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+dlg_handle_key (WDialog * h, int d_key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (h), d_key);
+ if (command == CK_IgnoreKey)
+ command = keybind_lookup_keymap_command (dialog_map, d_key);
+ if (command != CK_IgnoreKey)
+ return dlg_execute_cmd (h, command);
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dlg_key_event (WDialog * h, int d_key)
+{
+ Widget *w = WIDGET (h);
+ WGroup *g = GROUP (h);
+ cb_ret_t handled;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* TAB used to cycle */
+ if (!widget_get_options (w, WOP_WANT_TAB))
+ {
+ if (d_key == '\t')
+ {
+ group_select_next_widget (g);
+ return;
+ }
+ else if ((d_key & ~(KEY_M_SHIFT | KEY_M_CTRL)) == '\t')
+ {
+ group_select_prev_widget (g);
+ return;
+ }
+ }
+
+ /* first can dlalog handle the key itself */
+ handled = send_message (h, NULL, MSG_KEY, d_key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ handled = group_default_callback (w, NULL, MSG_KEY, d_key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ handled = dlg_handle_key (h, d_key);
+
+ (void) handled;
+ send_message (h, NULL, MSG_POST_KEY, d_key, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+dlg_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ if (w->mouse_callback != NULL)
+ {
+ int mou;
+
+ mou = mouse_handle_event (w, event);
+ if (mou != MOU_UNHANDLED)
+ return mou;
+ }
+
+ return group_handle_mouse_event (w, event);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frontend_dlg_run (WDialog * h)
+{
+ Widget *wh = WIDGET (h);
+ Gpm_Event event;
+
+ event.x = -1;
+
+ /* close opened editors, viewers, etc */
+ if (!widget_get_state (wh, WST_MODAL) && mc_global.midnight_shutdown)
+ {
+ send_message (h, NULL, MSG_VALIDATE, 0, NULL);
+ return;
+ }
+
+ while (widget_get_state (wh, WST_ACTIVE))
+ {
+ int d_key;
+
+ if (tty_got_winch ())
+ dialog_change_screen_size ();
+
+ if (is_idle ())
+ {
+ if (idle_hook)
+ execute_hooks (idle_hook);
+
+ while (widget_get_state (wh, WST_IDLE) && is_idle ())
+ send_message (wh, NULL, MSG_IDLE, 0, NULL);
+
+ /* Allow terminating the dialog from the idle handler */
+ if (!widget_get_state (wh, WST_ACTIVE))
+ break;
+ }
+
+ widget_update_cursor (wh);
+
+ /* Clear interrupt flag */
+ tty_got_interrupt ();
+ d_key = tty_get_event (&event, GROUP (h)->mouse_status == MOU_REPEAT, TRUE);
+
+ dlg_process_event (h, d_key, &event);
+
+ if (widget_get_state (wh, WST_CLOSED))
+ send_message (h, NULL, MSG_VALIDATE, 0, NULL);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+dlg_default_destroy (Widget * w)
+{
+ WDialog *h = DIALOG (w);
+
+ /* if some widgets have history, save all histories at one moment here */
+ dlg_save_history (h);
+ group_default_callback (w, NULL, MSG_DESTROY, 0, NULL);
+ mc_event_group_del (h->event_group);
+ g_free (h->event_group);
+ g_free (h);
+
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/** Default dialog callback */
+
+cb_ret_t
+dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* nothing to init in dialog itself */
+ return MSG_HANDLED;
+
+ case MSG_IDLE:
+ /* we don't want endless loop */
+ widget_idle (w, FALSE);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ /* nothing to deinit in dialog itself */
+ return MSG_HANDLED;
+
+ default:
+ return group_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_CLICK:
+ if (event->y < 0 || event->y >= w->rect.lines || event->x < 0 || event->x >= w->rect.cols)
+ {
+ DIALOG (w)->ret_value = B_CANCEL;
+ dlg_stop (DIALOG (w));
+ }
+ break;
+
+ default:
+ /* return MOU_UNHANDLED */
+ event->result.abort = TRUE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WDialog *
+dlg_create (gboolean modal, int y1, int x1, int lines, int cols, widget_pos_flags_t pos_flags,
+ gboolean compact, const int *colors, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback, const char *help_ctx, const char *title)
+{
+ WRect r = { y1, x1, lines, cols };
+ WDialog *new_d;
+ Widget *w;
+ WGroup *g;
+
+ new_d = g_new0 (WDialog, 1);
+ w = WIDGET (new_d);
+ g = GROUP (new_d);
+ widget_adjust_position (pos_flags, &r);
+ group_init (g, &r, callback != NULL ? callback : dlg_default_callback,
+ mouse_callback != NULL ? mouse_callback : dlg_default_mouse_callback);
+
+ w->pos_flags = pos_flags;
+ w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
+ w->state |= WST_FOCUSED;
+ /* Temporary hack: dialog doesn't have an owner, own itself. */
+ w->owner = g;
+
+ w->keymap = dialog_map;
+
+ w->mouse_handler = dlg_handle_mouse_event;
+ w->mouse.forced_capture = mouse_close_dialog && (w->pos_flags & WPOS_FULLSCREEN) == 0;
+
+ w->destroy = dlg_default_destroy;
+ w->get_colors = dlg_default_get_colors;
+
+ new_d->colors = colors;
+ new_d->help_ctx = help_ctx;
+ new_d->compact = compact;
+ new_d->data = NULL;
+
+ if (modal)
+ {
+ w->state |= WST_MODAL;
+
+ new_d->bg =
+ WIDGET (frame_new (0, 0, w->rect.lines, w->rect.cols, title, FALSE, new_d->compact));
+ group_add_widget (g, new_d->bg);
+ frame_set_title (FRAME (new_d->bg), title);
+ }
+
+ /* unique name of event group for this dialog */
+ new_d->event_group = g_strdup_printf ("%s_%p", MCEVENT_GROUP_DIALOG, (void *) new_d);
+
+ return new_d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_set_default_colors (void)
+{
+ dialog_colors[DLG_COLOR_NORMAL] = COLOR_NORMAL;
+ dialog_colors[DLG_COLOR_FOCUS] = COLOR_FOCUS;
+ dialog_colors[DLG_COLOR_HOT_NORMAL] = COLOR_HOT_NORMAL;
+ dialog_colors[DLG_COLOR_HOT_FOCUS] = COLOR_HOT_FOCUS;
+ dialog_colors[DLG_COLOR_TITLE] = COLOR_TITLE;
+
+ alarm_colors[DLG_COLOR_NORMAL] = ERROR_COLOR;
+ alarm_colors[DLG_COLOR_FOCUS] = ERROR_FOCUS;
+ alarm_colors[DLG_COLOR_HOT_NORMAL] = ERROR_HOT_NORMAL;
+ alarm_colors[DLG_COLOR_HOT_FOCUS] = ERROR_HOT_FOCUS;
+ alarm_colors[DLG_COLOR_TITLE] = ERROR_TITLE;
+
+ listbox_colors[DLG_COLOR_NORMAL] = PMENU_ENTRY_COLOR;
+ listbox_colors[DLG_COLOR_FOCUS] = PMENU_SELECTED_COLOR;
+ listbox_colors[DLG_COLOR_HOT_NORMAL] = PMENU_ENTRY_COLOR;
+ listbox_colors[DLG_COLOR_HOT_FOCUS] = PMENU_SELECTED_COLOR;
+ listbox_colors[DLG_COLOR_TITLE] = PMENU_TITLE_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+do_refresh (void)
+{
+ GList *d = top_dlg;
+
+ if (fast_refresh)
+ {
+ if (d != NULL)
+ widget_draw (WIDGET (d->data));
+ }
+ else
+ {
+ /* Search first fullscreen dialog */
+ for (; d != NULL; d = g_list_next (d))
+ if ((WIDGET (d->data)->pos_flags & WPOS_FULLSCREEN) != 0)
+ break;
+
+ /* when small dialog (i.e. error message) is created first,
+ there is no fullscreen dialog in the stack */
+ if (d == NULL)
+ d = g_list_last (top_dlg);
+
+ /* back to top dialog */
+ for (; d != NULL; d = g_list_previous (d))
+ widget_draw (WIDGET (d->data));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_stop (WDialog * h)
+{
+ widget_set_state (WIDGET (h), WST_CLOSED, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Init the process */
+
+void
+dlg_init (WDialog * h)
+{
+ WGroup *g = GROUP (h);
+ Widget *wh = WIDGET (h);
+
+ if (top_dlg != NULL && widget_get_state (WIDGET (top_dlg->data), WST_MODAL))
+ widget_set_state (wh, WST_MODAL, TRUE);
+
+ /* add dialog to the stack */
+ top_dlg = g_list_prepend (top_dlg, h);
+
+ /* Initialize dialog manager and widgets */
+ if (widget_get_state (wh, WST_CONSTRUCT))
+ {
+ if (!widget_get_state (wh, WST_MODAL))
+ dialog_switch_add (h);
+
+ send_message (h, NULL, MSG_INIT, 0, NULL);
+ group_default_callback (wh, NULL, MSG_INIT, 0, NULL);
+ dlg_read_history (h);
+ }
+
+ /* Select the first widget that takes focus */
+ while (g->current != NULL && !widget_is_focusable (g->current->data))
+ group_set_current_widget_next (g);
+
+ widget_set_state (wh, WST_ACTIVE, TRUE);
+ /* draw dialog and focus found widget */
+ widget_set_state (wh, WST_FOCUSED, TRUE);
+
+ h->ret_value = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+dlg_process_event (WDialog * h, int key, Gpm_Event * event)
+{
+ switch (key)
+ {
+ case EV_NONE:
+ if (tty_got_interrupt ())
+ dlg_execute_cmd (h, CK_Cancel);
+ break;
+
+ case EV_MOUSE:
+ {
+ Widget *w = WIDGET (h);
+
+ GROUP (h)->mouse_status = w->mouse_handler (w, event);
+ break;
+ }
+
+ default:
+ dlg_key_event (h, key);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Shutdown the dlg_run */
+
+void
+dlg_run_done (WDialog * h)
+{
+ top_dlg = g_list_remove (top_dlg, h);
+
+ if (widget_get_state (WIDGET (h), WST_CLOSED))
+ {
+ send_message (h, GROUP (h)->current == NULL ? NULL : WIDGET (GROUP (h)->current->data),
+ MSG_END, 0, NULL);
+ if (!widget_get_state (WIDGET (h), WST_MODAL))
+ dialog_switch_remove (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Standard run dialog routine
+ * We have to keep this routine small so that we can duplicate it's
+ * behavior on complex routines like the file routines, this way,
+ * they can call the dlg_process_event without rewriting all the code
+ */
+
+int
+dlg_run (WDialog * h)
+{
+ dlg_init (h);
+ frontend_dlg_run (h);
+ dlg_run_done (h);
+ return h->ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Write history to the ${XDG_CACHE_HOME}/mc/history file
+ */
+void
+dlg_save_history (WDialog * h)
+{
+ char *profile;
+ int i;
+
+ if (num_history_items_recorded == 0) /* this is how to disable */
+ return;
+
+ profile = mc_config_get_full_path (MC_HISTORY_FILE);
+ i = open (profile, O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
+ if (i != -1)
+ close (i);
+
+ /* Make sure the history is only readable by the user */
+ if (chmod (profile, S_IRUSR | S_IWUSR) != -1 || errno == ENOENT)
+ {
+ ev_history_load_save_t event_data;
+
+ event_data.cfg = mc_config_init (profile, FALSE);
+ event_data.receiver = NULL;
+
+ /* get all histories in dialog */
+ mc_event_raise (h->event_group, MCEVENT_HISTORY_SAVE, &event_data);
+
+ mc_config_save_file (event_data.cfg, NULL);
+ mc_config_deinit (event_data.cfg);
+ }
+
+ g_free (profile);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+dlg_get_title (const WDialog * h, size_t len)
+{
+ char *t;
+
+ if (h == NULL)
+ abort ();
+
+ if (h->get_title != NULL)
+ t = h->get_title (h, len);
+ else
+ t = g_strdup ("");
+
+ return t;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/dialog.h b/lib/widget/dialog.h
new file mode 100644
index 0000000..0ebf510
--- /dev/null
+++ b/lib/widget/dialog.h
@@ -0,0 +1,129 @@
+/*
+ Dialog box features module for the Midnight Commander
+ */
+
+/** \file dialog.h
+ * \brief Header: dialog box features module
+ */
+
+#ifndef MC__DIALOG_H
+#define MC__DIALOG_H
+
+#include <sys/types.h> /* size_t */
+
+#include "lib/global.h"
+#include "lib/hook.h" /* hook_t */
+#include "lib/keybind.h" /* global_keymap_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define DIALOG(x) ((WDialog *)(x))
+#define CONST_DIALOG(x) ((const WDialog *)(x))
+
+/* Common return values */
+/* ATTENTION: avoid overlapping with FileProgressStatus values */
+#define B_EXIT 0
+#define B_CANCEL 1
+#define B_ENTER 2
+#define B_HELP 3
+#define B_USER 100
+
+/*** enums ***************************************************************************************/
+
+/* Dialog color constants */
+typedef enum
+{
+ DLG_COLOR_NORMAL,
+ DLG_COLOR_FOCUS,
+ DLG_COLOR_HOT_NORMAL,
+ DLG_COLOR_HOT_FOCUS,
+ DLG_COLOR_TITLE,
+ DLG_COLOR_COUNT
+} dlg_colors_enum_t;
+
+/*** typedefs(not structures) ********************************************************************/
+
+typedef struct WDialog WDialog;
+
+/* get string representation of shortcut assigned with command */
+/* as menu is a widget of dialog, ask dialog about shortcut string */
+typedef char *(*dlg_shortcut_str) (long command);
+
+/* get dialog name to show in dialog list */
+typedef char *(*dlg_title_str) (const WDialog * h, size_t len);
+
+typedef int dlg_colors_t[DLG_COLOR_COUNT];
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WDialog
+{
+ WGroup group; /* base class */
+
+ /* Set by the user */
+ gboolean compact; /* Suppress spaces around the frame */
+ const char *help_ctx; /* Name of the help entry */
+ const int *colors; /* Color set. Unused in viewer and editor */
+
+ /* Set and received by the user */
+ int ret_value; /* Result of dlg_run() */
+
+ /* Internal variables */
+ void *data; /* Data can be passed to dialog */
+ char *event_group; /* Name of event group for this dialog */
+ Widget *bg; /* WFrame or WBackground */
+
+ dlg_shortcut_str get_shortcut; /* Shortcut string */
+ dlg_title_str get_title; /* useless for modal dialogs */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/* Color styles for normal and error dialogs */
+extern dlg_colors_t dialog_colors;
+extern dlg_colors_t alarm_colors;
+extern dlg_colors_t listbox_colors;
+
+extern GList *top_dlg;
+
+/* A hook list for idle events */
+extern hook_t *idle_hook;
+
+extern gboolean fast_refresh;
+extern gboolean mouse_close_dialog;
+
+extern const global_keymap_t *dialog_map;
+
+/*** declarations of public functions ************************************************************/
+
+/* Creates a dialog head */
+WDialog *dlg_create (gboolean modal, int y1, int x1, int lines, int cols,
+ widget_pos_flags_t pos_flags, gboolean compact,
+ const int *colors, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback,
+ const char *help_ctx, const char *title);
+
+void dlg_set_default_colors (void);
+
+void dlg_init (WDialog * h);
+int dlg_run (WDialog * d);
+
+void dlg_run_done (WDialog * h);
+void dlg_save_history (WDialog * h);
+void dlg_process_event (WDialog * h, int key, Gpm_Event * event);
+
+char *dlg_get_title (const WDialog * h, size_t len);
+
+/* Default callbacks for dialogs */
+cb_ret_t dlg_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void dlg_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+
+void dlg_stop (WDialog * h);
+
+/* Redraw all dialogs */
+void do_refresh (void);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__DIALOG_H */
diff --git a/lib/widget/frame.c b/lib/widget/frame.c
new file mode 100644
index 0000000..02f3b36
--- /dev/null
+++ b/lib/widget/frame.c
@@ -0,0 +1,162 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2020-2022
+ The Free Software Foundation, Inc.
+
+ Authors:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-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 frame.c
+ * \brief Source: WFrame widget (frame of dialogs)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* MC_PTR_FREE */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frame_adjust (WFrame * f)
+{
+ Widget *w = WIDGET (f);
+
+ w->rect = WIDGET (w->owner)->rect;
+ w->pos_flags |= WPOS_KEEP_ALL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+frame_draw (const WFrame * f)
+{
+ const Widget *wf = CONST_WIDGET (f);
+ const WRect *w = &wf->rect;
+ int d = f->compact ? 0 : 1;
+ const int *colors;
+
+ colors = widget_get_colors (wf);
+
+ if (mc_global.tty.shadows)
+ tty_draw_box_shadow (w->y, w->x, w->lines, w->cols, SHADOW_COLOR);
+
+ tty_setcolor (colors[FRAME_COLOR_NORMAL]);
+ tty_fill_region (w->y, w->x, w->lines, w->cols, ' ');
+ tty_draw_box (w->y + d, w->x + d, w->lines - 2 * d, w->cols - 2 * d, f->single);
+
+ if (f->title != NULL)
+ {
+ /* TODO: truncate long title */
+ tty_setcolor (colors[FRAME_COLOR_TITLE]);
+ widget_gotoyx (f, d, (w->cols - str_term_width1 (f->title)) / 2);
+ tty_print_string (f->title);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WFrame *
+frame_new (int y, int x, int lines, int cols, const char *title, gboolean single, gboolean compact)
+{
+ WRect r = { y, x, lines, cols };
+ WFrame *f;
+ Widget *w;
+
+ f = g_new (WFrame, 1);
+ w = WIDGET (f);
+ widget_init (w, &r, frame_callback, NULL);
+
+ f->single = single;
+ f->compact = compact;
+
+ f->title = NULL;
+ frame_set_title (f, title);
+
+ return f;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+frame_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WFrame *f = FRAME (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ frame_adjust (f);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ frame_draw (f);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_free (f->title);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+frame_set_title (WFrame * f, const char *title)
+{
+ MC_PTR_FREE (f->title);
+
+ /* Strip existing spaces, add one space before and after the title */
+ if (title != NULL && *title != '\0')
+ {
+ char *t;
+
+ t = g_strstrip (g_strdup (title));
+ if (*t != '\0')
+ f->title = g_strdup_printf (" %s ", t);
+ g_free (t);
+ }
+
+ widget_draw (WIDGET (f));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/frame.h b/lib/widget/frame.h
new file mode 100644
index 0000000..83e314e
--- /dev/null
+++ b/lib/widget/frame.h
@@ -0,0 +1,43 @@
+
+/** \file frame.h
+ * \brief Header: WFrame widget
+ */
+
+#ifndef MC__WIDGET_FRAME_H
+#define MC__WIDGET_FRAME_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define FRAME(x) ((WFrame *)(x))
+#define CONST_FRAME(x) ((const WFrame *)(x))
+
+#define FRAME_COLOR_NORMAL DLG_COLOR_NORMAL
+#define FRAME_COLOR_TITLE DLG_COLOR_TITLE
+
+/*** enums ***************************************************************************************/
+
+/*** typedefs(not structures) ********************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+
+ char *title;
+ gboolean single;
+ gboolean compact;
+} WFrame;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WFrame *frame_new (int y, int x, int lines, int cols, const char *title, gboolean single,
+ gboolean compact);
+cb_ret_t frame_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void frame_set_title (WFrame * f, const char *title);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_FRAME_H */
diff --git a/lib/widget/gauge.c b/lib/widget/gauge.c
new file mode 100644
index 0000000..327431a
--- /dev/null
+++ b/lib/widget/gauge.c
@@ -0,0 +1,174 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 gauge.c
+ * \brief Source: WGauge widget (progress indicator)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static cb_ret_t
+gauge_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGauge *g = GAUGE (w);
+ const int *colors;
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ colors = widget_get_colors (w);
+ widget_gotoyx (w, 0, 0);
+ if (!g->shown)
+ {
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s", w->rect.cols, "");
+ }
+ else
+ {
+ int gauge_len;
+ int percentage, columns;
+ int total = g->max;
+ int done = g->current;
+
+ if (total <= 0 || done < 0)
+ {
+ done = 0;
+ total = 100;
+ }
+ if (done > total)
+ done = total;
+ while (total > 65535)
+ {
+ total /= 256;
+ done /= 256;
+ }
+
+ gauge_len = w->rect.cols - 7; /* 7 positions for percentage */
+
+ percentage = (200 * done / total + 1) / 2;
+ columns = (2 * gauge_len * done / total + 1) / 2;
+ tty_print_char ('[');
+ if (g->from_left_to_right)
+ {
+ tty_setcolor (GAUGE_COLOR);
+ tty_printf ("%*s", columns, "");
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s] %3d%%", gauge_len - columns, "", percentage);
+ }
+ else
+ {
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("%*s", gauge_len - columns, "");
+ tty_setcolor (GAUGE_COLOR);
+ tty_printf ("%*s", columns, "");
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ tty_printf ("] %3d%%", percentage);
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WGauge *
+gauge_new (int y, int x, int cols, gboolean shown, int max, int current)
+{
+ WRect r = { y, x, 1, cols };
+ WGauge *g;
+ Widget *w;
+
+ g = g_new (WGauge, 1);
+ w = WIDGET (g);
+ widget_init (w, &r, gauge_callback, NULL);
+
+ g->shown = shown;
+ if (max == 0)
+ max = 1; /* I do not like division by zero :) */
+ g->max = max;
+ g->current = current;
+ g->from_left_to_right = TRUE;
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+gauge_set_value (WGauge * g, int max, int current)
+{
+ if (g->current == current && g->max == max)
+ return; /* Do not flicker */
+
+ if (max == 0)
+ max = 1; /* I do not like division by zero :) */
+ g->current = current;
+ g->max = max;
+ widget_draw (WIDGET (g));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+gauge_show (WGauge * g, gboolean shown)
+{
+ if (g->shown != shown)
+ {
+ g->shown = shown;
+ widget_draw (WIDGET (g));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/gauge.h b/lib/widget/gauge.h
new file mode 100644
index 0000000..e1f5d2c
--- /dev/null
+++ b/lib/widget/gauge.h
@@ -0,0 +1,36 @@
+
+/** \file gauge.h
+ * \brief Header: WGauge widget
+ */
+
+#ifndef MC__WIDGET_GAUGE_H
+#define MC__WIDGET_GAUGE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GAUGE(x) ((WGauge *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WGauge
+{
+ Widget widget;
+ gboolean shown;
+ int max;
+ int current;
+ gboolean from_left_to_right;
+} WGauge;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WGauge *gauge_new (int y, int x, int cols, gboolean shown, int max, int current);
+void gauge_set_value (WGauge * g, int max, int current);
+void gauge_show (WGauge * g, gboolean shown);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_GAUGE_H */
diff --git a/lib/widget/group.c b/lib/widget/group.c
new file mode 100644
index 0000000..9ff52d9
--- /dev/null
+++ b/lib/widget/group.c
@@ -0,0 +1,968 @@
+/*
+ Widget group features module for the Midnight Commander
+
+ Copyright (C) 2020-2022
+ The Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-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 group.c
+ * \brief Source: widget group features module
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/key.h" /* ALT() */
+
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/* Control widget positions in a group */
+typedef struct
+{
+ int shift_x;
+ int scale_x;
+ int shift_y;
+ int scale_y;
+} widget_shift_scale_t;
+
+typedef struct
+{
+ widget_state_t state;
+ gboolean enable;
+} widget_state_info_t;
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_init (void *data, void *user_data)
+{
+ (void) user_data;
+
+ send_message (WIDGET (data), NULL, MSG_INIT, 0, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static GList *
+group_get_next_or_prev_of (GList * list, gboolean next)
+{
+ GList *l = NULL;
+
+ if (list != NULL)
+ {
+ WGroup *owner = WIDGET (list->data)->owner;
+
+ if (owner != NULL)
+ {
+ if (next)
+ {
+ l = g_list_next (list);
+ if (l == NULL)
+ l = owner->widgets;
+ }
+ else
+ {
+ l = g_list_previous (list);
+ if (l == NULL)
+ l = g_list_last (owner->widgets);
+ }
+ }
+ }
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_select_next_or_prev (WGroup * g, gboolean next)
+{
+ if (g->widgets != NULL && g->current != NULL)
+ {
+ GList *l = g->current;
+
+ do
+ {
+ l = group_get_next_or_prev_of (l, next);
+ }
+ while (!widget_is_focusable (l->data) && l != g->current);
+
+ widget_select (l->data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_set_state (gpointer data, gpointer user_data)
+{
+ widget_state_info_t *state = (widget_state_info_t *) user_data;
+
+ widget_set_state (WIDGET (data), state->state, state->enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Send broadcast message to all widgets in the group that have specified options.
+ *
+ * @param g WGroup object
+ * @param msg message sent to widgets
+ * @param reverse if TRUE, send message in reverse order, FALSE -- in direct one.
+ * @param options if WOP_DEFAULT, the message is sent to all widgets. Else message is sent to widgets
+ * that have specified options.
+ */
+
+static void
+group_send_broadcast_msg_custom (WGroup * g, widget_msg_t msg, gboolean reverse,
+ widget_options_t options)
+{
+ GList *p, *first;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ p = group_get_next_or_prev_of (g->current, !reverse);
+ first = p;
+
+ do
+ {
+ Widget *w = WIDGET (p->data);
+
+ p = group_get_next_or_prev_of (p, !reverse);
+
+ if (options == WOP_DEFAULT || (options & w->options) != 0)
+ /* special case: don't draw invisible widgets */
+ if (msg != MSG_DRAW || widget_get_state (w, WST_VISIBLE))
+ send_message (w, NULL, msg, 0, NULL);
+ }
+ while (first != p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback to convert group coordinates from local (relative to owner) to global
+ * (relative to screen).
+ *
+ * @param w widget
+ */
+
+static void
+group_default_make_global (Widget * w, const WRect * delta)
+{
+ GList *iter;
+
+ if (delta != NULL)
+ {
+ /* change own coordinates */
+ widget_default_make_global (w, delta);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_global (WIDGET (iter->data), delta);
+ }
+ else if (w->owner != NULL)
+ {
+ WRect r = WIDGET (w->owner)->rect;
+
+ r.lines = 0;
+ r.cols = 0;
+ /* change own coordinates */
+ widget_default_make_global (w, &r);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_global (WIDGET (iter->data), &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback to convert group coordinates from global (relative to screen) to local
+ * (relative to owner).
+ *
+ * @param w widget
+ */
+
+static void
+group_default_make_local (Widget * w, const WRect * delta)
+{
+ GList *iter;
+
+ if (delta != NULL)
+ {
+ /* change own coordinates */
+ widget_default_make_local (w, delta);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_local (WIDGET (iter->data), delta);
+ }
+ else if (w->owner != NULL)
+ {
+ WRect r = WIDGET (w->owner)->rect;
+
+ r.lines = 0;
+ r.cols = 0;
+ /* change own coordinates */
+ widget_default_make_local (w, &r);
+ /* change child widget coordinates */
+ for (iter = GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ WIDGET (iter->data)->make_local (WIDGET (iter->data), &r);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget in the group.
+ *
+ * @param w WGroup object
+ * @param what widget to find
+ *
+ * @return holder of @what if found, NULL otherwise
+ */
+
+static GList *
+group_default_find (const Widget * w, const Widget * what)
+{
+ GList *w0;
+
+ w0 = widget_default_find (w, what);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find (WIDGET (iter->data), what);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget in the group using widget callback.
+ *
+ * @param w WGroup object
+ * @param cb widget callback
+ *
+ * @return widget object if found, NULL otherwise
+ */
+
+static Widget *
+group_default_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ Widget *w0;
+
+ w0 = widget_default_find_by_type (w, cb);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find_by_type (WIDGET (iter->data), cb);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default group callback function to find widget by widget ID in the group.
+ *
+ * @param w WGroup object
+ * @param id widget ID
+ *
+ * @return widget object if widget with specified id is found in group, NULL otherwise
+ */
+
+static Widget *
+group_default_find_by_id (const Widget * w, unsigned long id)
+{
+ Widget *w0;
+
+ w0 = widget_default_find_by_id (w, id);
+ if (w0 == NULL)
+ {
+ GList *iter;
+
+ for (iter = CONST_GROUP (w)->widgets; iter != NULL; iter = g_list_next (iter))
+ {
+ w0 = widget_find_by_id (WIDGET (iter->data), id);
+ if (w0 != NULL)
+ break;
+ }
+ }
+
+ return w0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update cursor position in the active widget of the group.
+ *
+ * @param g WGroup object
+ *
+ * @return MSG_HANDLED if cursor was updated in the specified group, MSG_NOT_HANDLED otherwise
+ */
+
+static cb_ret_t
+group_update_cursor (WGroup * g)
+{
+ GList *p = g->current;
+
+ if (p != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
+ do
+ {
+ Widget *w = WIDGET (p->data);
+
+ /* Don't use widget_is_selectable() here.
+ If WOP_SELECTABLE option is not set, widget can handle mouse events.
+ For example, commandl line in file manager */
+ if (widget_get_options (w, WOP_WANT_CURSOR) && widget_get_state (w, WST_VISIBLE)
+ && !widget_get_state (w, WST_DISABLED) && widget_update_cursor (WIDGET (p->data)))
+ return MSG_HANDLED;
+
+ p = group_get_widget_next_of (p);
+ }
+ while (p != g->current);
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_widget_set_position (gpointer data, gpointer user_data)
+{
+ /* there are, mainly, 2 generally possible situations:
+ * 1. control sticks to one side - it should be moved
+ * 2. control sticks to two sides of one direction - it should be sized
+ */
+
+ Widget *c = WIDGET (data);
+ const WRect *g = &CONST_WIDGET (c->owner)->rect;
+ const widget_shift_scale_t *wss = (const widget_shift_scale_t *) user_data;
+ WRect r = c->rect;
+
+ if ((c->pos_flags & WPOS_CENTER_HORZ) != 0)
+ r.x = g->x + (g->cols - c->rect.cols) / 2;
+ else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0 && (c->pos_flags & WPOS_KEEP_RIGHT) != 0)
+ {
+ r.x += wss->shift_x;
+ r.cols += wss->scale_x;
+ }
+ else if ((c->pos_flags & WPOS_KEEP_LEFT) != 0)
+ r.x += wss->shift_x;
+ else if ((c->pos_flags & WPOS_KEEP_RIGHT) != 0)
+ r.x += wss->shift_x + wss->scale_x;
+
+ if ((c->pos_flags & WPOS_CENTER_VERT) != 0)
+ r.y = g->y + (g->lines - c->rect.lines) / 2;
+ else if ((c->pos_flags & WPOS_KEEP_TOP) != 0 && (c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
+ {
+ r.y += wss->shift_y;
+ r.lines += wss->scale_y;
+ }
+ else if ((c->pos_flags & WPOS_KEEP_TOP) != 0)
+ r.y += wss->shift_y;
+ else if ((c->pos_flags & WPOS_KEEP_BOTTOM) != 0)
+ r.y += wss->shift_y + wss->scale_y;
+
+ send_message (c, NULL, MSG_RESIZE, 0, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_set_position (WGroup * g, const WRect * r)
+{
+ WRect *w = &WIDGET (g)->rect;
+ widget_shift_scale_t wss;
+ /* save old positions, will be used to reposition childs */
+ WRect or = *w;
+
+ *w = *r;
+
+ /* dialog is empty */
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* values by which controls should be moved */
+ wss.shift_x = w->x - or.x;
+ wss.scale_x = w->cols - or.cols;
+ wss.shift_y = w->y - or.y;
+ wss.scale_y = w->lines - or.lines;
+
+ if (wss.shift_x != 0 || wss.shift_y != 0 || wss.scale_x != 0 || wss.scale_y != 0)
+ g_list_foreach (g->widgets, group_widget_set_position, &wss);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_default_resize (WGroup * g, WRect * r)
+{
+ /* This is default resizing mechanism.
+ * The main idea of this code is to resize dialog according to flags
+ * (if any of flags require automatic resizing, like WPOS_CENTER,
+ * end after that reposition controls in dialog according to flags of widget)
+ */
+
+ Widget *w = WIDGET (g);
+ WRect r0;
+
+ r0 = r != NULL ? *r : w->rect;
+ widget_adjust_position (w->pos_flags, &r0);
+ group_set_position (g, &r0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+group_draw (WGroup * g)
+{
+ Widget *wg = WIDGET (g);
+
+ /* draw all widgets in Z-order, from first to last */
+ if (widget_get_state (wg, WST_ACTIVE))
+ {
+ GList *p;
+
+ if (g->winch_pending)
+ {
+ g->winch_pending = FALSE;
+ send_message (wg, NULL, MSG_RESIZE, 0, NULL);
+ }
+
+ for (p = g->widgets; p != NULL; p = g_list_next (p))
+ widget_draw (WIDGET (p->data));
+
+ widget_update_cursor (wg);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+group_handle_key (WGroup * g, int key)
+{
+ cb_ret_t handled;
+
+ /* first try the hotkey */
+ handled = send_message (g, NULL, MSG_HOTKEY, key, NULL);
+
+ /* not used - then try widget_callback */
+ if (handled == MSG_NOT_HANDLED)
+ handled = send_message (g->current->data, NULL, MSG_KEY, key, NULL);
+
+ /* not used - try to use the unhandled case */
+ if (handled == MSG_NOT_HANDLED)
+ handled = send_message (g, g->current->data, MSG_UNHANDLED_KEY, key, NULL);
+
+ return handled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+group_handle_hotkey (WGroup * g, int key)
+{
+ GList *current;
+ Widget *w;
+ cb_ret_t handled = MSG_NOT_HANDLED;
+ int c;
+
+ if (g->widgets == NULL)
+ return MSG_NOT_HANDLED;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ w = WIDGET (g->current->data);
+
+ if (!widget_get_state (w, WST_VISIBLE) || widget_get_state (w, WST_DISABLED))
+ return MSG_NOT_HANDLED;
+
+ /* Explanation: we don't send letter hotkeys to other widgets
+ * if the currently selected widget is an input line */
+ if (widget_get_options (w, WOP_IS_INPUT))
+ {
+ /* skip ascii control characters, anything else can valid character in some encoding */
+ if (key >= 32 && key < 256)
+ return MSG_NOT_HANDLED;
+ }
+
+ /* If it's an alt key, send the message */
+ c = key & ~ALT (0);
+ if (key & ALT (0) && g_ascii_isalpha (c))
+ key = g_ascii_tolower (c);
+
+ if (widget_get_options (w, WOP_WANT_HOTKEY))
+ handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
+
+ /* If not used, send hotkey to other widgets */
+ if (handled == MSG_HANDLED)
+ return MSG_HANDLED;
+
+ current = group_get_widget_next_of (g->current);
+
+ /* send it to all widgets */
+ while (g->current != current && handled == MSG_NOT_HANDLED)
+ {
+ w = WIDGET (current->data);
+
+ if (widget_get_options (w, WOP_WANT_HOTKEY) && !widget_get_state (w, WST_DISABLED))
+ handled = send_message (w, NULL, MSG_HOTKEY, key, NULL);
+
+ if (handled == MSG_NOT_HANDLED)
+ current = group_get_widget_next_of (current);
+ }
+
+ if (handled == MSG_HANDLED)
+ {
+ w = WIDGET (current->data);
+ widget_select (w);
+ send_message (g, w, MSG_HOTKEY_HANDLED, 0, NULL);
+ }
+
+ return handled;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Initialize group.
+ *
+ * @param g WGroup widget
+ * @param y1 y-coordinate of top-left corner
+ * @param x1 x-coordinate of top-left corner
+ * @param lines group height
+ * @param cols group width
+ * @param callback group callback
+ * @param mouse_callback group mouse handler
+ */
+
+void
+group_init (WGroup * g, const WRect * r, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback)
+{
+ Widget *w = WIDGET (g);
+
+ widget_init (w, r, callback != NULL ? callback : group_default_callback, mouse_callback);
+
+ w->mouse_handler = group_handle_mouse_event;
+
+ w->make_global = group_default_make_global;
+ w->make_local = group_default_make_local;
+
+ w->find = group_default_find;
+ w->find_by_type = group_default_find_by_type;
+ w->find_by_id = group_default_find_by_id;
+
+ w->set_state = group_default_set_state;
+
+ g->mouse_status = MOU_UNHANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroup *g = GROUP (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ g_list_foreach (g->widgets, group_widget_init, NULL);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ group_draw (g);
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ return group_handle_key (g, parm);
+
+ case MSG_HOTKEY:
+ return group_handle_hotkey (g, parm);
+
+ case MSG_CURSOR:
+ return group_update_cursor (g);
+
+ case MSG_RESIZE:
+ group_default_resize (g, RECT (data));
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_list_foreach (g->widgets, (GFunc) widget_destroy, NULL);
+ g_list_free (g->widgets);
+ g->widgets = NULL;
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Change state of group.
+ *
+ * @param w group
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+cb_ret_t
+group_default_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ gboolean ret = MSG_HANDLED;
+ WGroup *g = GROUP (w);
+ widget_state_info_t st = {
+ .state = state,
+ .enable = enable
+ };
+
+ ret = widget_default_set_state (w, state, enable);
+
+ if (state == WST_ACTIVE || state == WST_SUSPENDED || state == WST_CLOSED)
+ /* inform all child widgets */
+ g_list_foreach (g->widgets, group_widget_set_state, &st);
+
+ if ((w->state & WST_ACTIVE) != 0)
+ {
+ if ((w->state & WST_FOCUSED) != 0)
+ {
+ /* update current widget */
+ if (g->current != NULL)
+ widget_set_state (WIDGET (g->current->data), WST_FOCUSED, enable);
+ }
+ else
+ /* inform all child widgets */
+ g_list_foreach (g->widgets, group_widget_set_state, &st);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Handling mouse events.
+ *
+ * @param g WGroup object
+ * @param event GPM mouse event
+ *
+ * @return result of mouse event handling
+ */
+int
+group_handle_mouse_event (Widget * w, Gpm_Event * event)
+{
+ WGroup *g = GROUP (w);
+
+ if (g->widgets != NULL)
+ {
+ GList *p;
+
+ /* send the event to widgets in reverse Z-order */
+ p = g_list_last (g->widgets);
+ do
+ {
+ Widget *wp = WIDGET (p->data);
+
+ /* Don't use widget_is_selectable() here.
+ If WOP_SELECTABLE option is not set, widget can handle mouse events.
+ For example, commandl line in file manager */
+ if (widget_get_state (w, WST_VISIBLE) && !widget_get_state (wp, WST_DISABLED))
+ {
+ /* put global cursor position to the widget */
+ int ret;
+
+ ret = wp->mouse_handler (wp, event);
+ if (ret != MOU_UNHANDLED)
+ return ret;
+ }
+
+ p = g_list_previous (p);
+ }
+ while (p != NULL);
+ }
+
+ return MOU_UNHANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Insert widget to group before specified widget with specified positioning.
+ * Make the inserted widget current.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ * @pos positioning flags
+ * @param before add @w before this widget
+ *
+ * @return widget ID
+ */
+
+unsigned long
+group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags, const void *before)
+{
+ Widget *wg = WIDGET (g);
+ Widget *ww = WIDGET (w);
+ GList *new_current;
+
+ /* Don't accept NULL widget. This shouldn't happen */
+ assert (ww != NULL);
+
+ if ((pos_flags & WPOS_CENTER_HORZ) != 0)
+ ww->rect.x = (wg->rect.cols - ww->rect.cols) / 2;
+
+ if ((pos_flags & WPOS_CENTER_VERT) != 0)
+ ww->rect.y = (wg->rect.lines - ww->rect.lines) / 2;
+
+ ww->owner = g;
+ ww->pos_flags = pos_flags;
+ widget_make_global (ww);
+
+ if (g->widgets == NULL || before == NULL)
+ {
+ g->widgets = g_list_append (g->widgets, ww);
+ new_current = g_list_last (g->widgets);
+ }
+ else
+ {
+ GList *b;
+
+ b = g_list_find (g->widgets, before);
+
+ /* don't accept widget not from group. This shouldn't happen */
+ assert (b != NULL);
+
+ b = g_list_next (b);
+ g->widgets = g_list_insert_before (g->widgets, b, ww);
+ if (b != NULL)
+ new_current = g_list_previous (b);
+ else
+ new_current = g_list_last (g->widgets);
+ }
+
+ /* widget has been added at runtime */
+ if (widget_get_state (wg, WST_ACTIVE))
+ {
+ group_widget_init (ww, NULL);
+ widget_select (ww);
+ }
+ else
+ g->current = new_current;
+
+ return ww->id;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Remove widget from group.
+ *
+ * @param w Widget object
+ */
+void
+group_remove_widget (void *w)
+{
+ Widget *ww = WIDGET (w);
+ WGroup *g;
+ GList *d;
+
+ /* Don't accept NULL widget. This shouldn't happen */
+ assert (w != NULL);
+
+ g = ww->owner;
+
+ d = g_list_find (g->widgets, ww);
+ if (d == g->current)
+ group_set_current_widget_next (g);
+
+ g->widgets = g_list_delete_link (g->widgets, d);
+ if (g->widgets == NULL)
+ g->current = NULL;
+
+ /* widget has been deleted at runtime */
+ if (widget_get_state (WIDGET (g), WST_ACTIVE))
+ {
+ group_draw (g);
+ group_select_current_widget (g);
+ }
+
+ widget_make_local (ww);
+ ww->owner = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Switch current widget to widget after current in group.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_set_current_widget_next (WGroup * g)
+{
+ g->current = group_get_next_or_prev_of (g->current, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Switch current widget to widget before current in group.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_set_current_widget_prev (WGroup * g)
+{
+ g->current = group_get_next_or_prev_of (g->current, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get widget that is after specified widget in group.
+ *
+ * @param w widget holder
+ *
+ * @return widget that is after "w" or NULL if "w" is NULL or widget doesn't have owner
+ */
+
+GList *
+group_get_widget_next_of (GList * w)
+{
+ return group_get_next_or_prev_of (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get widget that is before specified widget in group.
+ *
+ * @param w widget holder
+ *
+ * @return widget that is before "w" or NULL if "w" is NULL or widget doesn't have owner
+ */
+
+GList *
+group_get_widget_prev_of (GList * w)
+{
+ return group_get_next_or_prev_of (w, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Try to select next widget in the Z order.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_select_next_widget (WGroup * g)
+{
+ group_select_next_or_prev (g, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Try to select previous widget in the Z order.
+ *
+ * @param g WGroup object
+ */
+
+void
+group_select_prev_widget (WGroup * g)
+{
+ group_select_next_or_prev (g, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find the widget with the specified ID in the group and select it
+ *
+ * @param g WGroup object
+ * @param id widget ID
+ */
+
+void
+group_select_widget_by_id (const WGroup * g, unsigned long id)
+{
+ Widget *w;
+
+ w = widget_find_by_id (CONST_WIDGET (g), id);
+ if (w != NULL)
+ widget_select (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Send broadcast message to all widgets in the group.
+ *
+ * @param g WGroup object
+ * @param msg message sent to widgets
+ */
+
+void
+group_send_broadcast_msg (WGroup * g, widget_msg_t msg)
+{
+ group_send_broadcast_msg_custom (g, msg, FALSE, WOP_DEFAULT);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/group.h b/lib/widget/group.h
new file mode 100644
index 0000000..3155d8f
--- /dev/null
+++ b/lib/widget/group.h
@@ -0,0 +1,125 @@
+/*
+ * Widget group features module for Midnight Commander
+ */
+
+/** \file group.h
+ * \brief Header: widget group features module
+ */
+
+#ifndef MC__GROUP_H
+#define MC__GROUP_H
+
+#include "lib/global.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GROUP(x) ((WGroup *)(x))
+#define CONST_GROUP(x) ((const WGroup *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** typedefs(not structures) ********************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WGroup
+{
+ Widget widget;
+
+ /* Group members */
+ GList *widgets; /* widgets list */
+ GList *current; /* Currently active widget */
+
+ gboolean winch_pending; /* SIGWINCH signal has been got. Resize group after rise */
+ int mouse_status; /* For the autorepeat status of the mouse */
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void group_init (WGroup * g, const WRect * r, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback);
+/* Default callback for groups */
+cb_ret_t group_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+cb_ret_t group_default_set_state (Widget * w, widget_state_t state, gboolean enable);
+int group_handle_mouse_event (Widget * w, Gpm_Event * event);
+
+unsigned long group_add_widget_autopos (WGroup * g, void *w, widget_pos_flags_t pos_flags,
+ const void *before);
+void group_remove_widget (void *w);
+
+void group_set_current_widget_next (WGroup * g);
+void group_set_current_widget_prev (WGroup * g);
+
+GList *group_get_widget_next_of (GList * w);
+GList *group_get_widget_prev_of (GList * w);
+
+void group_select_next_widget (WGroup * g);
+void group_select_prev_widget (WGroup * g);
+
+void group_select_widget_by_id (const WGroup * g, unsigned long id);
+
+void group_send_broadcast_msg (WGroup * g, widget_msg_t message);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Add widget to group before current widget.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ *
+ * @return widget ID
+ */
+
+static inline unsigned long
+group_add_widget (WGroup * g, void *w)
+{
+ return group_add_widget_autopos (g, w, WPOS_KEEP_DEFAULT,
+ g->current != NULL ? g->current->data : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Add widget to group before specified widget.
+ *
+ * @param g WGroup object
+ * @param w widget to be added
+ * @param before add @w before this widget
+ *
+ * @return widget ID
+ */
+
+static inline unsigned long
+group_add_widget_before (WGroup * g, void *w, void *before)
+{
+ return group_add_widget_autopos (g, w, WPOS_KEEP_DEFAULT, before);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Select current widget in the Dialog.
+ *
+ * @param h WDialog object
+ */
+
+static inline void
+group_select_current_widget (WGroup * g)
+{
+ if (g->current != NULL)
+ widget_select (WIDGET (g->current->data));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline unsigned long
+group_get_current_widget_id (const WGroup * g)
+{
+ return WIDGET (g->current->data)->id;
+}
+
+#endif /* MC__GROUP_H */
diff --git a/lib/widget/groupbox.c b/lib/widget/groupbox.c
new file mode 100644
index 0000000..ecd9f54
--- /dev/null
+++ b/lib/widget/groupbox.c
@@ -0,0 +1,132 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 groupbox.c
+ * \brief Source: WGroupbox widget
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static cb_ret_t
+groupbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WGroupbox *g = GROUPBOX (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ gboolean disabled;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+
+ disabled = widget_get_state (w, WST_DISABLED);
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL]);
+ tty_draw_box (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, TRUE);
+
+ if (g->title != NULL)
+ {
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_TITLE]);
+ widget_gotoyx (w, 0, 1);
+ tty_print_string (g->title);
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (g->title);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WGroupbox *
+groupbox_new (int y, int x, int height, int width, const char *title)
+{
+ WRect r = { y, x, height, width };
+ WGroupbox *g;
+ Widget *w;
+
+ g = g_new (WGroupbox, 1);
+ w = WIDGET (g);
+ widget_init (w, &r, groupbox_callback, NULL);
+
+ g->title = NULL;
+ groupbox_set_title (g, title);
+
+ return g;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+groupbox_set_title (WGroupbox * g, const char *title)
+{
+ MC_PTR_FREE (g->title);
+
+ /* Strip existing spaces, add one space before and after the title */
+ if (title != NULL && *title != '\0')
+ {
+ char *t;
+
+ t = g_strstrip (g_strdup (title));
+ g->title = g_strconcat (" ", t, " ", (char *) NULL);
+ g_free (t);
+ }
+
+ widget_draw (WIDGET (g));
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/groupbox.h b/lib/widget/groupbox.h
new file mode 100644
index 0000000..06fb0d3
--- /dev/null
+++ b/lib/widget/groupbox.h
@@ -0,0 +1,32 @@
+
+/** \file groupbox.h
+ * \brief Header: WGroupbox widget
+ */
+
+#ifndef MC__WIDGET_GROUPBOX_H
+#define MC__WIDGET_GROUPBOX_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define GROUPBOX(x) ((WGroupbox *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WGroupbox
+{
+ Widget widget;
+ char *title;
+} WGroupbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WGroupbox *groupbox_new (int y, int x, int height, int width, const char *title);
+void groupbox_set_title (WGroupbox * g, const char *title);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_GROUPBOX_H */
diff --git a/lib/widget/history.c b/lib/widget/history.c
new file mode 100644
index 0000000..deb92ab
--- /dev/null
+++ b/lib/widget/history.c
@@ -0,0 +1,298 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 history.c
+ * \brief Source: show history
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* LINES, COLS */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/keybind.h" /* CK_* */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define B_VIEW (B_USER + 1)
+#define B_EDIT (B_USER + 2)
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ int y;
+ int x;
+ size_t count;
+ size_t max_width;
+} history_dlg_data;
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static cb_ret_t
+history_dlg_reposition (WDialog * dlg_head)
+{
+ history_dlg_data *data;
+ int x = 0, y, he, wi;
+ WRect r;
+
+ /* guard checks */
+ if ((dlg_head == NULL) || (dlg_head->data == NULL))
+ return MSG_NOT_HANDLED;
+
+ data = (history_dlg_data *) dlg_head->data;
+
+ y = data->y;
+ he = data->count + 2;
+
+ if (he <= y || y > (LINES - 6))
+ {
+ he = MIN (he, y - 1);
+ y -= he;
+ }
+ else
+ {
+ y++;
+ he = MIN (he, LINES - y);
+ }
+
+ if (data->x > 2)
+ x = data->x - 2;
+
+ wi = data->max_width + 4;
+
+ if ((wi + x) > COLS)
+ {
+ wi = MIN (wi, COLS);
+ x = COLS - wi;
+ }
+
+ rect_init (&r, y, x, he, wi);
+
+ return dlg_default_callback (WIDGET (dlg_head), NULL, MSG_RESIZE, 0, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+history_dlg_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ return history_dlg_reposition (DIALOG (w));
+
+ case MSG_NOTIFY:
+ {
+ /* message from listbox */
+ WDialog *d = DIALOG (w);
+
+ switch (parm)
+ {
+ case CK_View:
+ d->ret_value = B_VIEW;
+ break;
+ case CK_Edit:
+ d->ret_value = B_EDIT;
+ break;
+ case CK_Enter:
+ d->ret_value = B_ENTER;
+ break;
+ default:
+ return MSG_NOT_HANDLED;
+ }
+
+ dlg_stop (d);
+ return MSG_HANDLED;
+ }
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+history_create_item (history_descriptor_t * hd, void *data)
+{
+ char *text = (char *) data;
+ size_t width;
+
+ width = str_term_width1 (text);
+ hd->max_width = MAX (width, hd->max_width);
+
+ listbox_add_item (hd->listbox, LISTBOX_APPEND_AT_END, 0, text, NULL, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+history_release_item (history_descriptor_t * hd, WLEntry * le)
+{
+ void *text;
+
+ (void) hd;
+
+ text = le->text;
+ le->text = NULL;
+
+ return text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+history_descriptor_init (history_descriptor_t * hd, int y, int x, GList * history, int current)
+{
+ hd->list = history;
+ hd->y = y;
+ hd->x = x;
+ hd->current = current;
+ hd->action = CK_IgnoreKey;
+ hd->text = NULL;
+ hd->max_width = 0;
+ hd->listbox = listbox_new (1, 1, 2, 2, TRUE, NULL);
+ /* in most cases history list contains string only and no any other data */
+ hd->create = history_create_item;
+ hd->release = history_release_item;
+ hd->free = g_free;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+history_show (history_descriptor_t * hd)
+{
+ GList *z, *hi;
+ size_t count;
+ WDialog *query_dlg;
+ history_dlg_data hist_data;
+ int dlg_ret;
+
+ if (hd == NULL || hd->list == NULL)
+ return;
+
+ hd->max_width = str_term_width1 (_("History")) + 2;
+
+ for (z = hd->list; z != NULL; z = g_list_previous (z))
+ hd->create (hd, z->data);
+ /* after this, the order of history items is following: recent at begin, oldest at end */
+
+ count = listbox_get_length (hd->listbox);
+
+ hist_data.y = hd->y;
+ hist_data.x = hd->x;
+ hist_data.count = count;
+ hist_data.max_width = hd->max_width;
+
+ query_dlg =
+ dlg_create (TRUE, 0, 0, 4, 4, WPOS_KEEP_DEFAULT, TRUE, dialog_colors, history_dlg_callback,
+ NULL, "[History-query]", _("History"));
+ query_dlg->data = &hist_data;
+
+ /* this call makes list stick to all sides of dialog, effectively make
+ it be resized with dialog */
+ group_add_widget_autopos (GROUP (query_dlg), hd->listbox, WPOS_KEEP_ALL, NULL);
+
+ /* to avoid diplicating of (calculating sizes in two places)
+ code, call history_dlg_callback function here, to set dialog and
+ controls positions.
+ The main idea - create 4x4 dialog and add 2x2 list in
+ center of it, and let dialog function resize it to needed size. */
+ send_message (query_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ if (WIDGET (query_dlg)->rect.y < hd->y)
+ {
+ /* history is above base widget -- revert order to place recent item at bottom */
+ /* revert history direction */
+ g_queue_reverse (hd->listbox->list);
+ if (hd->current < 0 || (size_t) hd->current >= count)
+ listbox_select_last (hd->listbox);
+ else
+ listbox_select_entry (hd->listbox, count - 1 - (size_t) hd->current);
+ }
+ else
+ {
+ /* history is below base widget -- keep order to place recent item on top */
+ if (hd->current > 0)
+ listbox_select_entry (hd->listbox, hd->current);
+ }
+
+ dlg_ret = dlg_run (query_dlg);
+ if (dlg_ret != B_CANCEL)
+ {
+ char *q;
+
+ switch (dlg_ret)
+ {
+ case B_EDIT:
+ hd->action = CK_Edit;
+ break;
+ case B_VIEW:
+ hd->action = CK_View;
+ break;
+ default:
+ hd->action = CK_Enter;
+ }
+
+ listbox_get_current (hd->listbox, &q, NULL);
+ hd->text = g_strdup (q);
+ }
+
+ /* get modified history from dialog */
+ z = NULL;
+ for (hi = listbox_get_first_link (hd->listbox); hi != NULL; hi = g_list_next (hi))
+ /* history is being reverted here again */
+ z = g_list_prepend (z, hd->release (hd, LENTRY (hi->data)));
+
+ /* restore history direction */
+ if (WIDGET (query_dlg)->rect.y < hd->y)
+ z = g_list_reverse (z);
+
+ widget_destroy (WIDGET (query_dlg));
+
+ hd->list = g_list_first (hd->list);
+ g_list_free_full (hd->list, hd->free);
+ hd->list = g_list_last (z);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/history.h b/lib/widget/history.h
new file mode 100644
index 0000000..9c4b403
--- /dev/null
+++ b/lib/widget/history.h
@@ -0,0 +1,51 @@
+
+/** \file lib/widget/history.h
+ * \brief Header: show history
+ */
+
+#ifndef MC__WIDGET_HISTORY_H
+#define MC__WIDGET_HISTORY_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* forward declarations */
+struct history_descriptor_t;
+struct WLEntry;
+struct WListbox;
+
+typedef void (*history_create_item_func) (struct history_descriptor_t * hd, void *data);
+typedef void *(*history_release_item_func) (struct history_descriptor_t * hd, struct WLEntry * le);
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct history_descriptor_t
+{
+ GList *list; /**< list with history items */
+ int y; /**< y-coordinate to place history window */
+ int x; /**< x-coordinate to place history window */
+ int current; /**< initially selected item in the history */
+ int action; /**< return action in the history */
+ char *text; /**< return text of selected item */
+
+ size_t max_width; /**< maximum width of sring in history */
+ struct WListbox *listbox; /**< listbox widget to draw history */
+
+ history_create_item_func create; /**< function to create item of @list */
+ history_release_item_func release; /**< function to release item of @list */
+ GDestroyNotify free; /**< function to destroy element of @list */
+} history_descriptor_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+void history_descriptor_init (history_descriptor_t * hd, int y, int x, GList * history,
+ int current);
+
+void history_show (history_descriptor_t * hd);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_HISTORY_H */
diff --git a/lib/widget/hline.c b/lib/widget/hline.c
new file mode 100644
index 0000000..867caf6
--- /dev/null
+++ b/lib/widget/hline.c
@@ -0,0 +1,192 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 hline.c
+ * \brief Source: WHLine widget (horizontal line)
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hline_adjust_cols (WHLine * l)
+{
+ if (l->auto_adjust_cols)
+ {
+ Widget *wl = WIDGET (l);
+ const Widget *o = CONST_WIDGET (wl->owner);
+ WRect *w = &wl->rect;
+ const WRect *wo = &o->rect;
+
+ if (CONST_DIALOG (o)->compact)
+ {
+ w->x = wo->x;
+ w->cols = wo->cols;
+ }
+ else
+ {
+ w->x = wo->x + 1;
+ w->cols = wo->cols - 2;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+hline_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WHLine *l = HLINE (w);
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ hline_adjust_cols (l);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ hline_adjust_cols (l);
+ w->rect.y = RECT (data)->y;
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ if (l->transparent)
+ tty_setcolor (DEFAULT_COLOR);
+ else
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (colors[DLG_COLOR_NORMAL]);
+ }
+
+ tty_draw_hline (w->rect.y, w->rect.x + 1, ACS_HLINE, w->rect.cols - 2);
+
+ if (l->auto_adjust_cols)
+ {
+ widget_gotoyx (w, 0, 0);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ widget_gotoyx (w, 0, w->rect.cols - 1);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+
+ if (l->text != NULL)
+ {
+ int text_width;
+
+ text_width = str_term_width1 (l->text);
+ widget_gotoyx (w, 0, (w->rect.cols - text_width) / 2);
+ tty_print_string (l->text);
+ }
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ g_free (l->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WHLine *
+hline_new (int y, int x, int width)
+{
+ WRect r = { y, x, 1, width };
+ WHLine *l;
+ Widget *w;
+
+ l = g_new (WHLine, 1);
+ w = WIDGET (l);
+ r.cols = width < 0 ? 1 : width;
+ widget_init (w, &r, hline_callback, NULL);
+ l->text = NULL;
+ l->auto_adjust_cols = (width < 0);
+ l->transparent = FALSE;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hline_set_text (WHLine * l, const char *text)
+{
+ g_free (l->text);
+
+ if (text == NULL || *text == '\0')
+ l->text = NULL;
+ else
+ l->text = g_strdup (text);
+
+ widget_draw (WIDGET (l));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hline_set_textv (WHLine * l, const char *format, ...)
+{
+ va_list args;
+ char buf[BUF_1K]; /* FIXME: is it enough? */
+
+ va_start (args, format);
+ g_vsnprintf (buf, sizeof (buf), format, args);
+ va_end (args);
+
+ hline_set_text (l, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/hline.h b/lib/widget/hline.h
new file mode 100644
index 0000000..4a84bf8
--- /dev/null
+++ b/lib/widget/hline.h
@@ -0,0 +1,37 @@
+
+/** \file hline.h
+ * \brief Header: WHLine widget
+ */
+
+#ifndef MC__WIDGET_HLINE_H
+#define MC__WIDGET_HLINE_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define HLINE(x) ((WHLine *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+ char *text;
+ gboolean auto_adjust_cols; /* Compute widget.cols from parent width? */
+ gboolean transparent; /* Paint in the default color fg/bg */
+} WHLine;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WHLine *hline_new (int y, int x, int width);
+void hline_set_text (WHLine * l, const char *text);
+/* *INDENT-OFF* */
+void hline_set_textv (WHLine * l, const char *format, ...) G_GNUC_PRINTF (2, 3);
+/* *INDENT-ON* */
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_HLINE_H */
diff --git a/lib/widget/input.c b/lib/widget/input.c
new file mode 100644
index 0000000..b58d602
--- /dev/null
+++ b/lib/widget/input.c
@@ -0,0 +1,1320 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 input.c
+ * \brief Source: WInput widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/fileloc.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+#include "lib/mcconfig.h" /* mc_config_history_*() */
+
+/*** global variables ****************************************************************************/
+
+gboolean quote = FALSE;
+
+const global_keymap_t *input_map = NULL;
+
+/* Color styles for input widgets */
+input_colors_t input_colors;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define LARGE_HISTORY_BUTTON 1
+
+#ifdef LARGE_HISTORY_BUTTON
+#define HISTORY_BUTTON_WIDTH 3
+#else
+#define HISTORY_BUTTON_WIDTH 1
+#endif
+
+#define should_show_history_button(in) \
+ (in->history.list != NULL && WIDGET (in)->rect.cols > HISTORY_BUTTON_WIDTH * 2 + 1 \
+ && WIDGET (in)->owner != NULL)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* Input widgets have a global kill ring */
+/* Pointer to killed data */
+static char *kill_buffer = NULL;
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static size_t
+get_history_length (GList * history)
+{
+ size_t len = 0;
+
+ for (; history != NULL; history = g_list_previous (history))
+ len++;
+
+ return len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+draw_history_button (WInput * in)
+{
+ char c;
+ gboolean disabled;
+
+ if (g_list_next (in->history.current) == NULL)
+ c = '^';
+ else if (g_list_previous (in->history.current) == NULL)
+ c = 'v';
+ else
+ c = '|';
+
+ widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH);
+ disabled = widget_get_state (WIDGET (in), WST_DISABLED);
+ tty_setcolor (disabled ? DISABLED_COLOR : in->color[WINPUTC_HISTORY]);
+
+#ifdef LARGE_HISTORY_BUTTON
+ tty_print_string ("[ ]");
+ widget_gotoyx (in, 0, WIDGET (in)->rect.cols - HISTORY_BUTTON_WIDTH + 1);
+#endif
+
+ tty_print_char (c);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_mark_cmd (WInput * in, gboolean mark)
+{
+ in->mark = mark ? in->point : -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+input_eval_marks (WInput * in, long *start_mark, long *end_mark)
+{
+ if (in->mark >= 0)
+ {
+ *start_mark = MIN (in->mark, in->point);
+ *end_mark = MAX (in->mark, in->point);
+ return TRUE;
+ }
+
+ *start_mark = *end_mark = -1;
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+do_show_hist (WInput * in)
+{
+ size_t len;
+ history_descriptor_t hd;
+
+ len = get_history_length (in->history.list);
+
+ history_descriptor_init (&hd, WIDGET (in)->rect.y, WIDGET (in)->rect.x, in->history.list,
+ g_list_position (in->history.list, in->history.list));
+ history_show (&hd);
+
+ /* in->history.list was destroyed in history_show().
+ * Apply new history and current postition to avoid use-after-free. */
+ in->history.list = hd.list;
+ in->history.current = in->history.list;
+ if (hd.text != NULL)
+ {
+ input_assign_text (in, hd.text);
+ g_free (hd.text);
+ }
+
+ /* Has history cleaned up or not? */
+ if (len != get_history_length (in->history.list))
+ in->history.changed = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Strip password from incomplete url (just user:pass@host without VFS prefix).
+ *
+ * @param url partial URL
+ * @return newly allocated string without password
+ */
+
+static char *
+input_history_strip_password (char *url)
+{
+ char *at, *delim, *colon;
+
+ at = strrchr (url, '@');
+ if (at == NULL)
+ return g_strdup (url);
+
+ /* TODO: handle ':' and '@' in password */
+
+ delim = strstr (url, VFS_PATH_URL_DELIMITER);
+ if (delim != NULL)
+ colon = strchr (delim + strlen (VFS_PATH_URL_DELIMITER), ':');
+ else
+ colon = strchr (url, ':');
+
+ /* if 'colon' before 'at', 'colon' delimits user and password: user:password@host */
+ /* if 'colon' after 'at', 'colon' delimits host and port: user@host:port */
+ if (colon != NULL && colon > at)
+ colon = NULL;
+
+ if (colon == NULL)
+ return g_strdup (url);
+ *colon = '\0';
+
+ return g_strconcat (url, at, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_push_history (WInput * in)
+{
+ char *t;
+ gboolean empty;
+
+ t = g_strstrip (input_get_text (in));
+ empty = *t == '\0';
+ if (!empty)
+ {
+ g_free (t);
+ t = input_get_text (in);
+
+ if (in->history.name != NULL && in->strip_password)
+ {
+ /*
+ We got string user:pass@host without any VFS prefixes
+ and vfs_path_to_str_flags (t, VPF_STRIP_PASSWORD) doesn't work.
+ Therefore we want to strip password in separate algorithm
+ */
+ char *url_with_stripped_password;
+
+ url_with_stripped_password = input_history_strip_password (t);
+ g_free (t);
+ t = url_with_stripped_password;
+ }
+ }
+
+ if (in->history.list == NULL || in->history.list->data == NULL
+ || strcmp (in->history.list->data, t) != 0 || in->history.changed)
+ {
+ in->history.list = list_append_unique (in->history.list, t);
+ in->history.current = in->history.list;
+ in->history.changed = TRUE;
+ }
+ else
+ g_free (t);
+
+ in->need_push = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+move_buffer_backward (WInput * in, int start, int end)
+{
+ int str_len;
+
+ str_len = str_length (in->buffer->str);
+ if (start >= str_len || end > str_len + 1)
+ return;
+
+ start = str_offset_to_pos (in->buffer->str, start);
+ end = str_offset_to_pos (in->buffer->str, end);
+ g_string_erase (in->buffer, start, end - start);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+beginning_of_line (WInput * in)
+{
+ in->point = 0;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+end_of_line (WInput * in)
+{
+ in->point = str_length (in->buffer->str);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_char (WInput * in)
+{
+ if (in->point > 0)
+ {
+ const char *act;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ in->point -= str_cprev_noncomb_char (&act, in->buffer->str);
+ }
+
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+forward_char (WInput * in)
+{
+ const char *act;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ if (act[0] != '\0')
+ in->point += str_cnext_noncomb_char (&act);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+forward_word (WInput * in)
+{
+ const char *p;
+
+ p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+
+ for (; p[0] != '\0' && (str_isspace (p) || str_ispunct (p)); in->point++)
+ str_cnext_char (&p);
+
+ for (; p[0] != '\0' && !str_isspace (p) && !str_ispunct (p); in->point++)
+ str_cnext_char (&p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_word (WInput * in)
+{
+ const char *p;
+
+ p = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+
+ for (; p != in->buffer->str; in->point--)
+ {
+ const char *p_tmp;
+
+ p_tmp = p;
+ str_cprev_char (&p);
+ if (!str_isspace (p) && !str_ispunct (p))
+ {
+ p = p_tmp;
+ break;
+ }
+ }
+
+ for (; p != in->buffer->str; in->point--)
+ {
+ str_cprev_char (&p);
+ if (str_isspace (p) || str_ispunct (p))
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+backward_delete (WInput * in)
+{
+ const char *act;
+ int start;
+
+ if (in->point == 0)
+ return;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ start = in->point - str_cprev_noncomb_char (&act, in->buffer->str);
+ move_buffer_backward (in, start, in->point);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+ in->point = start;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+copy_region (WInput * in, int start, int end)
+{
+ int first = MIN (start, end);
+ int last = MAX (start, end);
+
+ if (last == first)
+ {
+ /* Copy selected files to clipboard */
+ mc_event_raise (MCEVENT_GROUP_FILEMANAGER, "panel_save_current_file_to_clip_file", NULL);
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+ return;
+ }
+
+ g_free (kill_buffer);
+
+ first = str_offset_to_pos (in->buffer->str, first);
+ last = str_offset_to_pos (in->buffer->str, last);
+
+ kill_buffer = g_strndup (in->buffer->str + first, last - first);
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_to_file", kill_buffer);
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_to_ext_clip", NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+delete_region (WInput * in, int start, int end)
+{
+ int first = MIN (start, end);
+ int last = MAX (start, end);
+
+ input_mark_cmd (in, FALSE);
+ in->point = first;
+ move_buffer_backward (in, first, last);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+insert_char (WInput * in, int c_code)
+{
+ int res;
+ long m1, m2;
+ size_t ins_point;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+
+ if (c_code == -1)
+ return MSG_NOT_HANDLED;
+
+ if (in->charpoint >= MB_LEN_MAX)
+ return MSG_HANDLED;
+
+ in->charbuf[in->charpoint] = c_code;
+ in->charpoint++;
+
+ res = str_is_valid_char (in->charbuf, in->charpoint);
+ if (res < 0)
+ {
+ if (res != -2)
+ in->charpoint = 0; /* broken multibyte char, skip */
+ return MSG_HANDLED;
+ }
+
+ in->need_push = TRUE;
+ ins_point = str_offset_to_pos (in->buffer->str, in->point);
+ g_string_insert_len (in->buffer, ins_point, in->charbuf, in->charpoint);
+ in->point++;
+ in->charpoint = 0;
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+delete_char (WInput * in)
+{
+ const char *act;
+ int end;
+
+ act = in->buffer->str + str_offset_to_pos (in->buffer->str, in->point);
+ end = in->point + str_cnext_noncomb_char (&act);
+ move_buffer_backward (in, in->point, end);
+ in->charpoint = 0;
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_word (WInput * in)
+{
+ int old_point = in->point;
+ int new_point;
+
+ forward_word (in);
+ new_point = in->point;
+ in->point = old_point;
+
+ delete_region (in, old_point, new_point);
+ in->need_push = TRUE;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+back_kill_word (WInput * in)
+{
+ int old_point = in->point;
+ int new_point;
+
+ backward_word (in);
+ new_point = in->point;
+ in->point = old_point;
+
+ delete_region (in, old_point, new_point);
+ in->need_push = TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+yank (WInput * in)
+{
+ if (kill_buffer != NULL)
+ {
+ char *p;
+
+ in->charpoint = 0;
+ for (p = kill_buffer; *p != '\0'; p++)
+ insert_char (in, *p);
+ in->charpoint = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+kill_line (WInput * in)
+{
+ int chp;
+
+ chp = str_offset_to_pos (in->buffer->str, in->point);
+ g_free (kill_buffer);
+ kill_buffer = g_strndup (in->buffer->str + chp, in->buffer->len - chp);
+ g_string_set_size (in->buffer, chp);
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+clear_line (WInput * in)
+{
+ in->need_push = TRUE;
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->mark = -1;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+ins_from_clip (WInput * in)
+{
+ char *p = NULL;
+ ev_clipboard_text_from_file_t event_data = { NULL, FALSE };
+
+ /* try use external clipboard utility */
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_file_from_ext_clip", NULL);
+
+ event_data.text = &p;
+ mc_event_raise (MCEVENT_GROUP_CORE, "clipboard_text_from_file", &event_data);
+ if (event_data.ret)
+ {
+ char *pp;
+
+ for (pp = p; *pp != '\0'; pp++)
+ insert_char (in, *pp);
+
+ g_free (p);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hist_prev (WInput * in)
+{
+ GList *prev;
+
+ if (in->history.list == NULL)
+ return;
+
+ if (in->need_push)
+ input_push_history (in);
+
+ prev = g_list_previous (in->history.current);
+ if (prev != NULL)
+ {
+ input_assign_text (in, (char *) prev->data);
+ in->history.current = prev;
+ in->history.changed = TRUE;
+ in->need_push = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+hist_next (WInput * in)
+{
+ GList *next;
+
+ if (in->need_push)
+ {
+ input_push_history (in);
+ input_assign_text (in, "");
+ return;
+ }
+
+ if (in->history.list == NULL)
+ return;
+
+ next = g_list_next (in->history.current);
+ if (next == NULL)
+ {
+ input_assign_text (in, "");
+ in->history.current = in->history.list;
+ }
+ else
+ {
+ input_assign_text (in, (char *) next->data);
+ in->history.current = next;
+ in->history.changed = TRUE;
+ in->need_push = FALSE;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+port_region_marked_for_delete (WInput * in)
+{
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->first = FALSE;
+ in->charpoint = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+input_execute_cmd (WInput * in, long command)
+{
+ cb_ret_t res = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ /* a highlight command like shift-arrow */
+ if (in->mark < 0)
+ {
+ input_mark_cmd (in, FALSE); /* clear */
+ input_mark_cmd (in, TRUE); /* marking on */
+ }
+ break;
+ case CK_WordRight:
+ case CK_WordLeft:
+ case CK_Right:
+ case CK_Left:
+ if (in->mark >= 0)
+ input_mark_cmd (in, FALSE);
+ break;
+ default:
+ break;
+ }
+
+ switch (command)
+ {
+ case CK_Home:
+ case CK_MarkToHome:
+ beginning_of_line (in);
+ break;
+ case CK_End:
+ case CK_MarkToEnd:
+ end_of_line (in);
+ break;
+ case CK_Left:
+ case CK_MarkLeft:
+ backward_char (in);
+ break;
+ case CK_WordLeft:
+ case CK_MarkToWordBegin:
+ backward_word (in);
+ break;
+ case CK_Right:
+ case CK_MarkRight:
+ forward_char (in);
+ break;
+ case CK_WordRight:
+ case CK_MarkToWordEnd:
+ forward_word (in);
+ break;
+ case CK_BackSpace:
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+ else
+ backward_delete (in);
+ }
+ break;
+ case CK_Delete:
+ if (in->first)
+ port_region_marked_for_delete (in);
+ else
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ delete_region (in, m1, m2);
+ else
+ delete_char (in);
+ }
+ break;
+ case CK_DeleteToWordEnd:
+ kill_word (in);
+ break;
+ case CK_DeleteToWordBegin:
+ back_kill_word (in);
+ break;
+ case CK_Mark:
+ input_mark_cmd (in, TRUE);
+ break;
+ case CK_Remove:
+ delete_region (in, in->point, MAX (in->mark, 0));
+ break;
+ case CK_DeleteToEnd:
+ kill_line (in);
+ break;
+ case CK_Clear:
+ clear_line (in);
+ break;
+ case CK_Store:
+ copy_region (in, MAX (in->mark, 0), in->point);
+ break;
+ case CK_Cut:
+ {
+ long m;
+
+ m = MAX (in->mark, 0);
+ copy_region (in, m, in->point);
+ delete_region (in, in->point, m);
+ }
+ break;
+ case CK_Yank:
+ yank (in);
+ break;
+ case CK_Paste:
+ ins_from_clip (in);
+ break;
+ case CK_HistoryPrev:
+ hist_prev (in);
+ break;
+ case CK_HistoryNext:
+ hist_next (in);
+ break;
+ case CK_History:
+ do_show_hist (in);
+ break;
+ case CK_Complete:
+ input_complete (in);
+ break;
+ default:
+ res = MSG_NOT_HANDLED;
+ }
+
+ switch (command)
+ {
+ case CK_MarkLeft:
+ case CK_MarkRight:
+ case CK_MarkToWordBegin:
+ case CK_MarkToWordEnd:
+ case CK_MarkToHome:
+ case CK_MarkToEnd:
+ /* do nothing */
+ break;
+ default:
+ in->mark = -1;
+ break;
+ }
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_load" event handler */
+static gboolean
+input_load_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WInput *in = INPUT (init_data);
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ (void) event_group_name;
+ (void) event_name;
+
+ in->history.list = mc_config_history_load (ev->cfg, in->history.name);
+ in->history.current = in->history.list;
+
+ if (in->init_from_history)
+ {
+ const char *def_text = "";
+
+ if (in->history.list != NULL && in->history.list->data != NULL)
+ def_text = (const char *) in->history.list->data;
+
+ input_assign_text (in, def_text);
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* "history_save" event handler */
+static gboolean
+input_save_history (const gchar * event_group_name, const gchar * event_name,
+ gpointer init_data, gpointer data)
+{
+ WInput *in = INPUT (init_data);
+
+ (void) event_group_name;
+ (void) event_name;
+
+ if (!in->is_password && (DIALOG (WIDGET (in)->owner)->ret_value != B_CANCEL))
+ {
+ ev_history_load_save_t *ev = (ev_history_load_save_t *) data;
+
+ input_push_history (in);
+ if (in->history.changed)
+ mc_config_history_save (ev->cfg, in->history.name, in->history.list);
+ in->history.changed = FALSE;
+ }
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_destroy (WInput * in)
+{
+ input_complete_free (in);
+
+ /* clean history */
+ if (in->history.list != NULL)
+ {
+ /* history is already saved before this moment */
+ in->history.list = g_list_first (in->history.list);
+ g_list_free_full (in->history.list, g_free);
+ }
+ g_free (in->history.name);
+ g_string_free (in->buffer, TRUE);
+ MC_PTR_FREE (kill_buffer);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Calculates the buffer index (aka "point") corresponding to some screen coordinate.
+ */
+static int
+input_screen_to_point (const WInput * in, int x)
+{
+ x += in->term_first_shown;
+
+ if (x < 0)
+ return 0;
+
+ if (x < str_term_width1 (in->buffer->str))
+ return str_column_to_pos (in->buffer->str, x);
+
+ return str_length (in->buffer->str);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+input_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ /* save point between MSG_MOUSE_DOWN and MSG_MOUSE_DRAG */
+ static int prev_point = 0;
+ WInput *in = INPUT (w);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+
+ if (event->x >= w->rect.cols - HISTORY_BUTTON_WIDTH && should_show_history_button (in))
+ do_show_hist (in);
+ else
+ {
+ in->first = FALSE;
+ input_mark_cmd (in, FALSE);
+ input_set_point (in, input_screen_to_point (in, event->x));
+ /* save point for the possible following MSG_MOUSE_DRAG action */
+ prev_point = in->point;
+ }
+ break;
+
+ case MSG_MOUSE_DRAG:
+ /* start point: set marker using point before first MSG_MOUSE_DRAG action */
+ if (in->mark < 0)
+ in->mark = prev_point;
+
+ input_set_point (in, input_screen_to_point (in, event->x));
+ break;
+
+ default:
+ /* don't create highlight region of 0 length */
+ if (in->mark == in->point)
+ input_mark_cmd (in, FALSE);
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Create new instance of WInput object.
+ * @param y Y coordinate
+ * @param x X coordinate
+ * @param input_colors Array of used colors
+ * @param width Widget width
+ * @param def_text Default text filled in widget
+ * @param histname Name of history
+ * @param completion_flags Flags for specify type of completions
+ * @return WInput object
+ */
+WInput *
+input_new (int y, int x, const int *colors, int width, const char *def_text,
+ const char *histname, input_complete_t completion_flags)
+{
+ WRect r = { y, x, 1, width };
+ WInput *in;
+ Widget *w;
+
+ in = g_new (WInput, 1);
+ w = WIDGET (in);
+ widget_init (w, &r, input_callback, input_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_IS_INPUT | WOP_WANT_CURSOR;
+ w->keymap = input_map;
+
+ in->color = colors;
+ in->first = TRUE;
+ in->mark = -1;
+ in->term_first_shown = 0;
+ in->disable_update = 0;
+ in->is_password = FALSE;
+ in->strip_password = FALSE;
+
+ /* in->buffer will be corrected in "history_load" event handler */
+ in->buffer = g_string_sized_new (width);
+
+ /* init completions before input_assign_text() call */
+ in->completions = NULL;
+ in->completion_flags = completion_flags;
+
+ in->init_from_history = (def_text == INPUT_LAST_TEXT);
+
+ if (in->init_from_history || def_text == NULL)
+ def_text = "";
+
+ input_assign_text (in, def_text);
+
+ /* prepare to history setup */
+ in->history.list = NULL;
+ in->history.current = NULL;
+ in->history.changed = FALSE;
+ in->history.name = NULL;
+ if ((histname != NULL) && (*histname != '\0'))
+ in->history.name = g_strdup (histname);
+ /* history will be loaded later */
+
+ in->label = NULL;
+
+ return in;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WInput *in = INPUT (w);
+ WDialog *h = DIALOG (w->owner);
+ cb_ret_t v;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ /* subscribe to "history_load" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w, NULL);
+ /* subscribe to "history_save" event */
+ mc_event_add (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w, NULL);
+ if (in->label != NULL)
+ widget_set_state (WIDGET (in->label), WST_DISABLED, widget_get_state (w, WST_DISABLED));
+ return MSG_HANDLED;
+
+ case MSG_KEY:
+ if (parm == XCTRL ('q'))
+ {
+ quote = TRUE;
+ v = input_handle_char (in, ascii_alpha_to_cntrl (tty_getch ()));
+ quote = FALSE;
+ return v;
+ }
+
+ /* Keys we want others to handle */
+ if (parm == KEY_UP || parm == KEY_DOWN || parm == ESC_CHAR
+ || parm == KEY_F (10) || parm == '\n')
+ return MSG_NOT_HANDLED;
+
+ /* When pasting multiline text, insert literal Enter */
+ if ((parm & ~KEY_M_MASK) == '\n')
+ {
+ quote = TRUE;
+ v = input_handle_char (in, '\n');
+ quote = FALSE;
+ return v;
+ }
+
+ return input_handle_char (in, parm);
+
+ case MSG_ACTION:
+ return input_execute_cmd (in, parm);
+
+ case MSG_DRAW:
+ input_update (in, FALSE);
+ return MSG_HANDLED;
+
+ case MSG_ENABLE:
+ case MSG_DISABLE:
+ if (in->label != NULL)
+ widget_set_state (WIDGET (in->label), WST_DISABLED, msg == MSG_DISABLE);
+ return MSG_HANDLED;
+
+ case MSG_CURSOR:
+ widget_gotoyx (in, 0, str_term_width2 (in->buffer->str, in->point) - in->term_first_shown);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ /* unsubscribe from "history_load" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_LOAD, input_load_history, w);
+ /* unsubscribe from "history_save" event */
+ mc_event_del (h->event_group, MCEVENT_HISTORY_SAVE, input_save_history, w);
+ input_destroy (in);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_set_default_colors (void)
+{
+ input_colors[WINPUTC_MAIN] = INPUT_COLOR;
+ input_colors[WINPUTC_MARK] = INPUT_MARK_COLOR;
+ input_colors[WINPUTC_UNCHANGED] = INPUT_UNCHANGED_COLOR;
+ input_colors[WINPUTC_HISTORY] = INPUT_HISTORY_COLOR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+input_handle_char (WInput * in, int key)
+{
+ cb_ret_t v;
+ long command;
+
+ if (quote)
+ {
+ input_complete_free (in);
+ v = insert_char (in, key);
+ input_update (in, TRUE);
+ quote = FALSE;
+ return v;
+ }
+
+ command = widget_lookup_key (WIDGET (in), key);
+ if (command == CK_IgnoreKey)
+ {
+ if (key > 255)
+ return MSG_NOT_HANDLED;
+ if (in->first)
+ port_region_marked_for_delete (in);
+ input_complete_free (in);
+ v = insert_char (in, key);
+ input_update (in, TRUE);
+ }
+ else
+ {
+ gboolean keep_first;
+
+ if (command != CK_Complete)
+ input_complete_free (in);
+ input_execute_cmd (in, command);
+ v = MSG_HANDLED;
+ /* if in->first == TRUE and history or completion window was cancelled,
+ keep "first" state */
+ keep_first = in->first && (command == CK_History || command == CK_Complete);
+ input_update (in, !keep_first);
+ }
+
+ return v;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_assign_text (WInput * in, const char *text)
+{
+ if (text == NULL)
+ text = "";
+
+ input_complete_free (in);
+ in->mark = -1;
+ in->need_push = TRUE;
+ in->charpoint = 0;
+ g_string_assign (in->buffer, text);
+ in->point = str_length (in->buffer->str);
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Inserts text in input line */
+void
+input_insert (WInput * in, const char *text, gboolean insert_extra_space)
+{
+ input_disable_update (in);
+ while (*text != '\0')
+ input_handle_char (in, (unsigned char) *text++); /* unsigned extension char->int */
+ if (insert_extra_space)
+ input_handle_char (in, ' ');
+ input_enable_update (in);
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_set_point (WInput * in, int pos)
+{
+ int max_pos;
+
+ max_pos = str_length (in->buffer->str);
+ pos = MIN (pos, max_pos);
+ if (pos != in->point)
+ input_complete_free (in);
+ in->point = pos;
+ in->charpoint = 0;
+ input_update (in, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_update (WInput * in, gboolean clear_first)
+{
+ Widget *wi = WIDGET (in);
+ const WRect *w = &wi->rect;
+ int has_history = 0;
+ int buf_len;
+ const char *cp;
+ int pw;
+
+ if (in->disable_update != 0)
+ return;
+
+ /* don't draw widget not put into dialog */
+ if (wi->owner == NULL || !widget_get_state (WIDGET (wi->owner), WST_ACTIVE))
+ return;
+
+ if (clear_first)
+ in->first = FALSE;
+
+ if (should_show_history_button (in))
+ has_history = HISTORY_BUTTON_WIDTH;
+
+ buf_len = str_length (in->buffer->str);
+
+ /* Adjust the mark */
+ in->mark = MIN (in->mark, buf_len);
+
+ pw = str_term_width2 (in->buffer->str, in->point);
+
+ /* Make the point visible */
+ if ((pw < in->term_first_shown) || (pw >= in->term_first_shown + w->cols - has_history))
+ {
+ in->term_first_shown = pw - (w->cols / 3);
+ if (in->term_first_shown < 0)
+ in->term_first_shown = 0;
+ }
+
+ if (has_history != 0)
+ draw_history_button (in);
+
+ if (widget_get_state (wi, WST_DISABLED))
+ tty_setcolor (DISABLED_COLOR);
+ else if (in->first)
+ tty_setcolor (in->color[WINPUTC_UNCHANGED]);
+ else
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+
+ widget_gotoyx (in, 0, 0);
+
+ if (!in->is_password)
+ {
+ if (in->mark < 0)
+ tty_print_string (str_term_substring (in->buffer->str, in->term_first_shown,
+ w->cols - has_history));
+ else
+ {
+ long m1, m2;
+
+ if (input_eval_marks (in, &m1, &m2))
+ {
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+ cp = str_term_substring (in->buffer->str, in->term_first_shown,
+ w->cols - has_history);
+ tty_print_string (cp);
+ tty_setcolor (in->color[WINPUTC_MARK]);
+ if (m1 < in->term_first_shown)
+ {
+ widget_gotoyx (in, 0, 0);
+ m1 = in->term_first_shown;
+ m2 -= m1;
+ }
+ else
+ {
+ int buf_width;
+
+ widget_gotoyx (in, 0, m1 - in->term_first_shown);
+ buf_width = str_term_width2 (in->buffer->str, m1);
+ m2 = MIN (m2 - m1,
+ (w->cols - has_history) - (buf_width - in->term_first_shown));
+ }
+
+ tty_print_string (str_term_substring (in->buffer->str, m1, m2));
+ }
+ }
+ }
+ else
+ {
+ int i;
+
+ cp = str_term_substring (in->buffer->str, in->term_first_shown, w->cols - has_history);
+ tty_setcolor (in->color[WINPUTC_MAIN]);
+ for (i = 0; i < w->cols - has_history; i++)
+ {
+ if (i < (buf_len - in->term_first_shown) && cp[0] != '\0')
+ tty_print_char ('*');
+ else
+ tty_print_char (' ');
+ if (cp[0] != '\0')
+ str_cnext_char (&cp);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_enable_update (WInput * in)
+{
+ in->disable_update--;
+ input_update (in, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_disable_update (WInput * in)
+{
+ in->disable_update++;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Cleans the input line and adds the current text to the history
+ *
+ * @param in the input line
+ */
+void
+input_clean (WInput * in)
+{
+ input_push_history (in);
+ in->need_push = TRUE;
+ g_string_set_size (in->buffer, 0);
+ in->point = 0;
+ in->charpoint = 0;
+ in->mark = -1;
+ input_complete_free (in);
+ input_update (in, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/input.h b/lib/widget/input.h
new file mode 100644
index 0000000..5dc433e
--- /dev/null
+++ b/lib/widget/input.h
@@ -0,0 +1,155 @@
+
+/** \file input.h
+ * \brief Header: WInput widget
+ */
+
+#ifndef MC__WIDGET_INPUT_H
+#define MC__WIDGET_INPUT_H
+
+#include <limits.h> /* MB_LEN_MAX */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define INPUT(x) ((WInput *)(x))
+
+/* For history load-save functions */
+#define INPUT_LAST_TEXT ((char *) 2)
+
+/*** enums ***************************************************************************************/
+
+typedef enum
+{
+ WINPUTC_MAIN, /* color used */
+ WINPUTC_MARK, /* color for marked text */
+ WINPUTC_UNCHANGED, /* color for inactive text (Is first keystroke) */
+ WINPUTC_HISTORY, /* color for history list */
+ WINPUTC_COUNT_COLORS /* count of used colors */
+} input_colors_enum_t;
+
+/* completion flags */
+typedef enum
+{
+ INPUT_COMPLETE_NONE = 0,
+ INPUT_COMPLETE_FILENAMES = 1 << 0,
+ INPUT_COMPLETE_HOSTNAMES = 1 << 1,
+ INPUT_COMPLETE_COMMANDS = 1 << 2,
+ INPUT_COMPLETE_VARIABLES = 1 << 3,
+ INPUT_COMPLETE_USERNAMES = 1 << 4,
+ INPUT_COMPLETE_CD = 1 << 5,
+ INPUT_COMPLETE_SHELL_ESC = 1 << 6,
+} input_complete_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef int input_colors_t[WINPUTC_COUNT_COLORS];
+
+typedef struct
+{
+ Widget widget;
+
+ GString *buffer;
+ const int *color;
+ int point; /* cursor position in the input line in characters */
+ int mark; /* the mark position in characters; negative value means no marked text */
+ int term_first_shown; /* column of the first shown character */
+ gboolean first; /* is first keystroke? */
+ int disable_update; /* do we want to skip updates? */
+ gboolean is_password; /* is this a password input line? */
+ gboolean init_from_history; /* init text will be get from history */
+ gboolean need_push; /* need to push the current Input on hist? */
+ gboolean strip_password; /* need to strip password before placing string to history */
+ char **completions; /* possible completions array */
+ input_complete_t completion_flags;
+ char charbuf[MB_LEN_MAX]; /* buffer for multibytes characters */
+ size_t charpoint; /* point to end of mulibyte sequence in charbuf */
+ WLabel *label; /* label associated with this input line */
+ struct input_history_t
+ {
+ char *name; /* name of history for loading and saving */
+ GList *list; /* the history */
+ GList *current; /* current history item */
+ gboolean changed; /* the history has changed */
+ } history;
+} WInput;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern int quote;
+
+extern const global_keymap_t *input_map;
+
+/* Color styles for normal and command line input widgets */
+extern input_colors_t input_colors;
+
+/*** declarations of public functions ************************************************************/
+
+WInput *input_new (int y, int x, const int *colors,
+ int len, const char *text, const char *histname,
+ input_complete_t completion_flags);
+/* callback is public; needed for command line */
+cb_ret_t input_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data);
+void input_set_default_colors (void);
+cb_ret_t input_handle_char (WInput * in, int key);
+void input_assign_text (WInput * in, const char *text);
+void input_insert (WInput * in, const char *text, gboolean insert_extra_space);
+void input_set_point (WInput * in, int pos);
+void input_update (WInput * in, gboolean clear_first);
+void input_enable_update (WInput * in);
+void input_disable_update (WInput * in);
+void input_clean (WInput * in);
+
+/* input_complete.c */
+void input_complete (WInput * in);
+void input_complete_free (WInput * in);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get text of input line.
+ *
+ * @param in input line
+ *
+ * @return newly allocated string that contains a copy of @in's text.
+ */
+static inline char *
+input_get_text (const WInput * in)
+{
+ return g_strndup (in->buffer->str, in->buffer->len);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get pointer to input line buffer.
+ *
+ * @param in input line
+ *
+ * @return pointer to @in->buffer->str.
+ */
+static inline const char *
+input_get_ctext (const WInput * in)
+{
+ return in->buffer->str;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Is input line empty or not.
+ *
+ * @param in input line
+ *
+ * @return TRUE if buffer of @in is empty, FALSE othewise.
+ */
+static inline gboolean
+input_is_empty (const WInput * in)
+{
+ return (in->buffer->len == 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+#endif /* MC__WIDGET_INPUT_H */
diff --git a/lib/widget/input_complete.c b/lib/widget/input_complete.c
new file mode 100644
index 0000000..c7fa555
--- /dev/null
+++ b/lib/widget/input_complete.c
@@ -0,0 +1,1482 @@
+/*
+ Input line filename/username/hostname/variable/command completion.
+ (Let mc type for you...)
+
+ Copyright (C) 1995-2022
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 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 lib/widget/input_complete.c
+ * \brief Source: Input line filename/username/hostname/variable/command completion
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <limits.h> /* MB_LEN_MAX */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* XCTRL and ALT macros */
+#include "lib/vfs/vfs.h"
+#include "lib/strescape.h"
+#include "lib/strutil.h"
+#include "lib/util.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/* Linux declares environ in <unistd.h>, so don't repeat it here. */
+#if (!(defined(__linux__) && defined (__USE_GNU)) && !defined(__CYGWIN__))
+extern char **environ;
+#endif
+
+/*** file scope macro definitions ****************************************************************/
+
+/* #define DO_COMPLETION_DEBUG */
+#ifdef DO_COMPLETION_DEBUG
+#define SHOW_C_CTX(func) fprintf(stderr, "%s: text='%s' flags=%s\n", func, text, show_c_flags(flags))
+#else
+#define SHOW_C_CTX(func)
+#endif /* DO_CMPLETION_DEBUG */
+
+#define DO_INSERTION 1
+#define DO_QUERY 2
+
+/*** file scope type declarations ****************************************************************/
+
+typedef char *CompletionFunction (const char *text, int state, input_complete_t flags);
+
+typedef struct
+{
+ size_t in_command_position;
+ char *word;
+ char *p;
+ char *q;
+ char *r;
+ gboolean is_cd;
+ input_complete_t flags;
+} try_complete_automation_state_t;
+
+/*** file scope variables ************************************************************************/
+
+static char **hosts = NULL;
+static char **hosts_p = NULL;
+static int hosts_alloclen = 0;
+
+static int complete_height, complete_width;
+static WInput *input;
+static int min_end;
+static int start = 0;
+static int end = 0;
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+char **try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags);
+void complete_engine_fill_completions (WInput * in);
+
+#ifdef DO_COMPLETION_DEBUG
+/**
+ * Useful to print/debug completion flags
+ */
+static const char *
+show_c_flags (input_complete_t flags)
+{
+ static char s_cf[] = "FHCVUDS";
+
+ s_cf[0] = (flags & INPUT_COMPLETE_FILENAMES) != 0 ? 'F' : ' ';
+ s_cf[1] = (flags & INPUT_COMPLETE_HOSTNAMES) != 0 ? 'H' : ' ';
+ s_cf[2] = (flags & INPUT_COMPLETE_COMMANDS) != 0 ? 'C' : ' ';
+ s_cf[3] = (flags & INPUT_COMPLETE_VARIABLES) != 0 ? 'V' : ' ';
+ s_cf[4] = (flags & INPUT_COMPLETE_USERNAMES) != 0 ? 'U' : ' ';
+ s_cf[5] = (flags & INPUT_COMPLETE_CD) != 0 ? 'D' : ' ';
+ s_cf[6] = (flags & INPUT_COMPLETE_SHELL_ESC) != 0 ? 'S' : ' ';
+
+ return s_cf;
+}
+#endif /* DO_CMPLETION_DEBUG */
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+filename_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static DIR *directory = NULL;
+ static char *filename = NULL;
+ static char *dirname = NULL;
+ static char *users_dirname = NULL;
+ static size_t filename_len = 0;
+ static vfs_path_t *dirname_vpath = NULL;
+
+ gboolean isdir = TRUE, isexec = FALSE;
+ struct vfs_dirent *entry = NULL;
+
+ SHOW_C_CTX ("filename_completion_function");
+
+ if (text != NULL && (flags & INPUT_COMPLETE_SHELL_ESC) != 0)
+ {
+ char *u_text;
+ char *result;
+ char *e_result;
+
+ u_text = strutils_shell_unescape (text);
+
+ result = filename_completion_function (u_text, state, flags & (~INPUT_COMPLETE_SHELL_ESC));
+ g_free (u_text);
+
+ e_result = strutils_shell_escape (result);
+ g_free (result);
+
+ return e_result;
+ }
+
+ /* If we're starting the match process, initialize us a bit. */
+ if (state == 0)
+ {
+ const char *temp;
+
+ g_free (dirname);
+ g_free (filename);
+ g_free (users_dirname);
+ vfs_path_free (dirname_vpath, TRUE);
+
+ if ((*text != '\0') && (temp = strrchr (text, PATH_SEP)) != NULL)
+ {
+ filename = g_strdup (++temp);
+ dirname = g_strndup (text, temp - text);
+ }
+ else
+ {
+ dirname = g_strdup (".");
+ filename = g_strdup (text);
+ }
+
+ /* We aren't done yet. We also support the "~user" syntax. */
+
+ /* Save the version of the directory that the user typed. */
+ users_dirname = dirname;
+ dirname = tilde_expand (dirname);
+ canonicalize_pathname (dirname);
+ dirname_vpath = vfs_path_from_str (dirname);
+
+ /* Here we should do something with variable expansion
+ and `command`.
+ Maybe a dream - UNIMPLEMENTED yet. */
+
+ directory = mc_opendir (dirname_vpath);
+ filename_len = strlen (filename);
+ }
+
+ /* Now that we have some state, we can read the directory. */
+
+ while (directory != NULL && (entry = mc_readdir (directory)) != NULL)
+ {
+ if (!str_is_valid_string (entry->d_name))
+ continue;
+
+ /* Special case for no filename.
+ All entries except "." and ".." match. */
+ if (filename_len == 0)
+ {
+ if (DIR_IS_DOT (entry->d_name) || DIR_IS_DOTDOT (entry->d_name))
+ continue;
+ }
+ else
+ {
+ /* Otherwise, if these match up to the length of filename, then
+ it may be a match. */
+ if ((entry->d_name[0] != filename[0]) ||
+ ((NLENGTH (entry)) < filename_len) ||
+ strncmp (filename, entry->d_name, filename_len) != 0)
+ continue;
+ }
+
+ isdir = TRUE;
+ isexec = FALSE;
+
+ {
+ struct stat tempstat;
+ vfs_path_t *tmp_vpath;
+
+ tmp_vpath = vfs_path_build_filename (dirname, entry->d_name, (char *) NULL);
+
+ /* Unix version */
+ if (mc_stat (tmp_vpath, &tempstat) == 0)
+ {
+ uid_t my_uid;
+ gid_t my_gid;
+
+ my_uid = getuid ();
+ my_gid = getgid ();
+
+ if (!S_ISDIR (tempstat.st_mode))
+ {
+ isdir = FALSE;
+
+ if ((my_uid == 0 && (tempstat.st_mode & 0111) != 0) ||
+ (my_uid == tempstat.st_uid && (tempstat.st_mode & 0100) != 0) ||
+ (my_gid == tempstat.st_gid && (tempstat.st_mode & 0010) != 0) ||
+ (tempstat.st_mode & 0001) != 0)
+ isexec = TRUE;
+ }
+ }
+ else
+ {
+ /* stat failed, strange. not a dir in any case */
+ isdir = FALSE;
+ }
+ vfs_path_free (tmp_vpath, TRUE);
+ }
+
+ if ((flags & INPUT_COMPLETE_COMMANDS) != 0 && (isexec || isdir))
+ break;
+ if ((flags & INPUT_COMPLETE_CD) != 0 && isdir)
+ break;
+ if ((flags & INPUT_COMPLETE_FILENAMES) != 0)
+ break;
+ }
+
+ if (entry == NULL)
+ {
+ if (directory != NULL)
+ {
+ mc_closedir (directory);
+ directory = NULL;
+ }
+ MC_PTR_FREE (dirname);
+ vfs_path_free (dirname_vpath, TRUE);
+ dirname_vpath = NULL;
+ MC_PTR_FREE (filename);
+ MC_PTR_FREE (users_dirname);
+ return NULL;
+ }
+
+ {
+ GString *temp;
+
+ temp = g_string_sized_new (16);
+
+ if (users_dirname != NULL && (users_dirname[0] != '.' || users_dirname[1] != '\0'))
+ {
+ g_string_append (temp, users_dirname);
+
+ /* We need a '/' at the end. */
+ if (!IS_PATH_SEP (temp->str[temp->len - 1]))
+ g_string_append_c (temp, PATH_SEP);
+ }
+ g_string_append (temp, entry->d_name);
+ if (isdir)
+ g_string_append_c (temp, PATH_SEP);
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** We assume here that text[0] == '~' , if you want to call it in another way,
+ you have to change the code */
+
+static char *
+username_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static struct passwd *entry = NULL;
+ static size_t userlen = 0;
+
+ (void) flags;
+ SHOW_C_CTX ("username_completion_function");
+
+ if (text[0] == '\\' && text[1] == '~')
+ text++;
+ if (state == 0)
+ { /* Initialization stuff */
+ setpwent ();
+ userlen = strlen (text + 1);
+ }
+
+ while ((entry = getpwent ()) != NULL)
+ {
+ /* Null usernames should result in all users as possible completions. */
+ if (userlen == 0)
+ break;
+ if (text[1] == entry->pw_name[0] && strncmp (text + 1, entry->pw_name, userlen) == 0)
+ break;
+ }
+
+ if (entry != NULL)
+ return g_strconcat ("~", entry->pw_name, PATH_SEP_STR, (char *) NULL);
+
+ endpwent ();
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** We assume text [0] == '$' and want to have a look at text [1], if it is
+ equal to '{', so that we should append '}' at the end */
+
+static char *
+variable_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static char **env_p = NULL;
+ static gboolean isbrace = FALSE;
+ static size_t varlen = 0;
+ const char *p = NULL;
+
+ (void) flags;
+ SHOW_C_CTX ("variable_completion_function");
+
+ if (state == 0)
+ { /* Initialization stuff */
+ isbrace = (text[1] == '{');
+ varlen = strlen (text + 1 + isbrace);
+ env_p = environ;
+ }
+
+ while (*env_p != NULL)
+ {
+ p = strchr (*env_p, '=');
+ if (p != NULL && ((size_t) (p - *env_p) >= varlen)
+ && strncmp (text + 1 + isbrace, *env_p, varlen) == 0)
+ break;
+ env_p++;
+ }
+
+ if (*env_p == NULL)
+ return NULL;
+
+ {
+ GString *temp;
+
+ temp = g_string_new_len (*env_p, p - *env_p);
+
+ if (isbrace)
+ {
+ g_string_prepend_c (temp, '{');
+ g_string_append_c (temp, '}');
+ }
+ g_string_prepend_c (temp, '$');
+
+ env_p++;
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+fetch_hosts (const char *filename)
+{
+ FILE *file;
+ char buffer[256];
+ char *name;
+ char *lc_start;
+ char *bi;
+
+ file = fopen (filename, "r");
+ if (file == NULL)
+ return;
+
+ while (fgets (buffer, sizeof (buffer) - 1, file) != NULL)
+ {
+ /* Skip to first character. */
+ for (bi = buffer; bi[0] != '\0' && str_isspace (bi); str_next_char (&bi))
+ ;
+
+ /* Ignore comments... */
+ if (bi[0] == '#')
+ continue;
+
+ /* Handle $include. */
+ if (strncmp (bi, "$include ", 9) == 0)
+ {
+ char *includefile, *t;
+
+ /* Find start of filename. */
+ includefile = bi + 9;
+ while (*includefile != '\0' && whitespace (*includefile))
+ includefile++;
+ t = includefile;
+
+ /* Find end of filename. */
+ while (t[0] != '\0' && !str_isspace (t))
+ str_next_char (&t);
+ *t = '\0';
+
+ fetch_hosts (includefile);
+ continue;
+ }
+
+ /* Skip IP #s. */
+ while (bi[0] != '\0' && !str_isspace (bi))
+ str_next_char (&bi);
+
+ /* Get the host names separated by white space. */
+ while (bi[0] != '\0' && bi[0] != '#')
+ {
+ while (bi[0] != '\0' && str_isspace (bi))
+ str_next_char (&bi);
+ if (bi[0] == '#')
+ continue;
+ for (lc_start = bi; bi[0] != '\0' && !str_isspace (bi); str_next_char (&bi))
+ ;
+
+ if (bi == lc_start)
+ continue;
+
+ name = g_strndup (lc_start, bi - lc_start);
+
+ {
+ char **host_p;
+ int j;
+
+ j = hosts_p - hosts;
+
+ if (j >= hosts_alloclen)
+ {
+ hosts_alloclen += 30;
+ hosts = g_renew (char *, hosts, hosts_alloclen + 1);
+ hosts_p = hosts + j;
+ }
+
+ for (host_p = hosts; host_p < hosts_p; host_p++)
+ if (strcmp (name, *host_p) == 0)
+ break; /* We do not want any duplicates */
+
+ if (host_p == hosts_p)
+ {
+ *(hosts_p++) = name;
+ *hosts_p = NULL;
+ }
+ else
+ g_free (name);
+ }
+ }
+ }
+
+ fclose (file);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+hostname_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static char **host_p = NULL;
+ static size_t textstart = 0;
+ static size_t textlen = 0;
+
+ (void) flags;
+ SHOW_C_CTX ("hostname_completion_function");
+
+ if (state == 0)
+ { /* Initialization stuff */
+ const char *p;
+
+ g_strfreev (hosts);
+ hosts_alloclen = 30;
+ hosts = g_new (char *, hosts_alloclen + 1);
+ *hosts = NULL;
+ hosts_p = hosts;
+ p = getenv ("HOSTFILE");
+ fetch_hosts (p != NULL ? p : "/etc/hosts");
+ host_p = hosts;
+ textstart = (*text == '@') ? 1 : 0;
+ textlen = strlen (text + textstart);
+ }
+
+ for (; *host_p != NULL; host_p++)
+ {
+ if (textlen == 0)
+ break; /* Match all of them */
+ if (strncmp (text + textstart, *host_p, textlen) == 0)
+ break;
+ }
+
+ if (*host_p == NULL)
+ {
+ g_strfreev (hosts);
+ hosts = NULL;
+ return NULL;
+ }
+
+ {
+ GString *temp;
+
+ temp = g_string_sized_new (8);
+
+ if (textstart != 0)
+ g_string_append_c (temp, '@');
+ g_string_append (temp, *host_p);
+ host_p++;
+
+ return g_string_free (temp, FALSE);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * This is the function to call when the word to complete is in a position
+ * where a command word can be found. It looks around $PATH, looking for
+ * commands that match. It also scans aliases, function names, and the
+ * table of shell built-ins.
+ */
+
+static char *
+command_completion_function (const char *text, int state, input_complete_t flags)
+{
+ static const char *path_end = NULL;
+ static gboolean isabsolute = FALSE;
+ static int phase = 0;
+ static size_t text_len = 0;
+ static const char *const *words = NULL;
+ static char *path = NULL;
+ static char *cur_path = NULL;
+ static char *cur_word = NULL;
+ static int init_state = 0;
+ static const char *const bash_reserved[] = {
+ "if", "then", "else", "elif", "fi", "case", "esac", "for",
+ "select", "while", "until", "do", "done", "in", "function", 0
+ };
+ static const char *const bash_builtins[] = {
+ "alias", "bg", "bind", "break", "builtin", "cd", "command",
+ "continue", "declare", "dirs", "echo", "enable", "eval",
+ "exec", "exit", "export", "fc", "fg", "getopts", "hash",
+ "help", "history", "jobs", "kill", "let", "local", "logout",
+ "popd", "pushd", "pwd", "read", "readonly", "return", "set",
+ "shift", "source", "suspend", "test", "times", "trap", "type",
+ "typeset", "ulimit", "umask", "unalias", "unset", "wait", 0
+ };
+
+ char *u_text;
+ char *p, *found;
+
+ SHOW_C_CTX ("command_completion_function");
+
+ if ((flags & INPUT_COMPLETE_COMMANDS) == 0)
+ return NULL;
+
+ u_text = strutils_shell_unescape (text);
+ flags &= ~INPUT_COMPLETE_SHELL_ESC;
+
+ if (state == 0)
+ { /* Initialize us a little bit */
+ isabsolute = strchr (u_text, PATH_SEP) != NULL;
+ if (!isabsolute)
+ {
+ words = bash_reserved;
+ phase = 0;
+ text_len = strlen (u_text);
+
+ if (path == NULL)
+ {
+ path = g_strdup (getenv ("PATH"));
+ if (path != NULL)
+ {
+ p = path;
+ path_end = strchr (p, '\0');
+ while ((p = strchr (p, PATH_ENV_SEP)) != NULL)
+ *p++ = '\0';
+ }
+ }
+ }
+ }
+
+ if (isabsolute)
+ {
+ p = filename_completion_function (u_text, state, flags);
+
+ if (p != NULL)
+ {
+ char *temp_p = p;
+
+ p = strutils_shell_escape (p);
+ g_free (temp_p);
+ }
+
+ g_free (u_text);
+ return p;
+ }
+
+ found = NULL;
+ switch (phase)
+ {
+ case 0: /* Reserved words */
+ for (; *words != NULL; words++)
+ if (strncmp (*words, u_text, text_len) == 0)
+ {
+ g_free (u_text);
+ return g_strdup (*(words++));
+ }
+ phase++;
+ words = bash_builtins;
+ MC_FALLTHROUGH;
+ case 1: /* Builtin commands */
+ for (; *words != NULL; words++)
+ if (strncmp (*words, u_text, text_len) == 0)
+ {
+ g_free (u_text);
+ return g_strdup (*(words++));
+ }
+ phase++;
+ if (path == NULL)
+ break;
+ cur_path = path;
+ cur_word = NULL;
+ MC_FALLTHROUGH;
+ case 2: /* And looking through the $PATH */
+ while (found == NULL)
+ {
+ if (cur_word == NULL)
+ {
+ char *expanded;
+
+ if (cur_path >= path_end)
+ break;
+ expanded = tilde_expand (*cur_path != '\0' ? cur_path : ".");
+ cur_word = mc_build_filename (expanded, u_text, (char *) NULL);
+ g_free (expanded);
+ canonicalize_pathname (cur_word);
+ cur_path = strchr (cur_path, '\0') + 1;
+ init_state = state;
+ }
+ found = filename_completion_function (cur_word, state - init_state, flags);
+ if (found == NULL)
+ MC_PTR_FREE (cur_word);
+ }
+ MC_FALLTHROUGH;
+ default:
+ break;
+ }
+
+ if (found == NULL)
+ MC_PTR_FREE (path);
+ else
+ {
+ p = strrchr (found, PATH_SEP);
+ if (p != NULL)
+ {
+ char *tmp = found;
+
+ found = strutils_shell_escape (p + 1);
+ g_free (tmp);
+ }
+ }
+
+ g_free (u_text);
+ return found;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+match_compare (const void *a, const void *b)
+{
+ return strcmp (*(char *const *) a, *(char *const *) b);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Returns an array of char * matches with the longest common denominator
+ in the 1st entry. Then a NULL terminated list of different possible
+ completions follows.
+ You have to supply your own CompletionFunction with the word you
+ want to complete as the first argument and an count of previous matches
+ as the second.
+ In case no matches were found we return NULL. */
+
+static char **
+completion_matches (const char *text, CompletionFunction entry_function, input_complete_t flags)
+{
+ /* Number of slots in match_list. */
+ size_t match_list_size = 30;
+ /* The list of matches. */
+ char **match_list;
+ /* Number of matches actually found. */
+ size_t matches = 0;
+
+ /* Temporary string binder. */
+ char *string;
+
+ match_list = g_new (char *, match_list_size + 1);
+ match_list[1] = NULL;
+
+ while ((string = (*entry_function) (text, matches, flags)) != NULL)
+ {
+ if (matches + 1 == match_list_size)
+ {
+ match_list_size += 30;
+ match_list = (char **) g_renew (char *, match_list, match_list_size + 1);
+ }
+ match_list[++matches] = string;
+ match_list[matches + 1] = NULL;
+ }
+
+ /* If there were any matches, then look through them finding out the
+ lowest common denominator. That then becomes match_list[0]. */
+ if (matches == 0)
+ MC_PTR_FREE (match_list); /* There were no matches. */
+ else
+ {
+ /* If only one match, just use that. */
+ if (matches == 1)
+ {
+ match_list[0] = match_list[1];
+ match_list[1] = NULL;
+ }
+ else
+ {
+ size_t i = 1;
+ int low = 4096; /* Count of max-matched characters. */
+ size_t j;
+
+ qsort (match_list + 1, matches, sizeof (char *), match_compare);
+
+ /* And compare each member of the list with
+ the next, finding out where they stop matching.
+ If we find two equal strings, we have to put one away... */
+
+ j = i + 1;
+ while (j < matches + 1)
+ {
+ char *si, *sj;
+ char *ni, *nj;
+
+ for (si = match_list[i], sj = match_list[j]; si[0] != '\0' && sj[0] != '\0';)
+ {
+
+ ni = str_get_next_char (si);
+ nj = str_get_next_char (sj);
+
+ if (ni - si != nj - sj)
+ break;
+ if (strncmp (si, sj, ni - si) != 0)
+ break;
+
+ si = ni;
+ sj = nj;
+ }
+
+ if (si[0] == '\0' && sj[0] == '\0')
+ { /* Two equal strings */
+ g_free (match_list[j]);
+ j++;
+ if (j > matches)
+ break;
+ continue; /* Look for a run of equal strings */
+ }
+ else if (low > si - match_list[i])
+ low = si - match_list[i];
+ if (i + 1 != j) /* So there's some gap */
+ match_list[i + 1] = match_list[j];
+ i++;
+ j++;
+ }
+ matches = i;
+ match_list[matches + 1] = NULL;
+ match_list[0] = g_strndup (match_list[1], low);
+ }
+ }
+
+ return match_list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Check if directory completion is needed */
+static gboolean
+check_is_cd (const char *text, int lc_start, input_complete_t flags)
+{
+ const char *p, *q;
+
+ SHOW_C_CTX ("check_is_cd");
+
+ if ((flags & INPUT_COMPLETE_CD) == 0)
+ return FALSE;
+
+ /* Skip initial spaces */
+ p = text;
+ q = text + lc_start;
+ while (p < q && p[0] != '\0' && str_isspace (p))
+ str_cnext_char (&p);
+
+ /* Check if the command is "cd" and the cursor is after it */
+ return (p[0] == 'c' && p[1] == 'd' && str_isspace (p + 2) && p + 2 < q);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+try_complete_commands_prepare (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+ const char *command_separator_chars = ";|&{(`";
+ char *ti;
+
+ if (*lc_start == 0)
+ ti = text;
+ else
+ {
+ ti = str_get_prev_char (&text[*lc_start]);
+ while (ti > text && whitespace (ti[0]))
+ str_prev_char (&ti);
+ }
+
+ if (ti == text)
+ state->in_command_position++;
+ else if (strchr (command_separator_chars, ti[0]) != NULL)
+ {
+ state->in_command_position++;
+ if (ti != text)
+ {
+ int this_char, prev_char;
+
+ /* Handle the two character tokens '>&', '<&', and '>|'.
+ We are not in a command position after one of these. */
+ this_char = ti[0];
+ prev_char = str_get_prev_char (ti)[0];
+
+ /* Quoted */
+ if ((this_char == '&' && (prev_char == '<' || prev_char == '>'))
+ || (this_char == '|' && prev_char == '>') || (ti != text
+ && str_get_prev_char (ti)[0] == '\\'))
+ state->in_command_position = 0;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+try_complete_find_start_sign (try_complete_automation_state_t * state)
+{
+ if ((state->flags & INPUT_COMPLETE_COMMANDS) != 0)
+ state->p = strrchr (state->word, '`');
+ if ((state->flags & (INPUT_COMPLETE_COMMANDS | INPUT_COMPLETE_VARIABLES)) != 0)
+ {
+ state->q = strrchr (state->word, '$');
+
+ /* don't substitute variable in \$ case */
+ if (strutils_is_char_escaped (state->word, state->q))
+ {
+ /* drop '\\' */
+ str_move (state->q - 1, state->q);
+ /* adjust flags */
+ state->flags &= ~INPUT_COMPLETE_VARIABLES;
+ state->q = NULL;
+ }
+ }
+ if ((state->flags & INPUT_COMPLETE_HOSTNAMES) != 0)
+ state->r = strrchr (state->word, '@');
+ if (state->q != NULL && state->q[1] == '(' && (state->flags & INPUT_COMPLETE_COMMANDS) != 0)
+ {
+ if (state->q > state->p)
+ state->p = str_get_next_char (state->q);
+ state->q = NULL;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char **
+try_complete_all_possible (try_complete_automation_state_t * state, char *text, int *lc_start)
+{
+ char **matches = NULL;
+
+ if (state->in_command_position != 0)
+ {
+ SHOW_C_CTX ("try_complete:cmd_subst");
+ matches =
+ completion_matches (state->word, command_completion_function,
+ state->flags & (~INPUT_COMPLETE_FILENAMES));
+ }
+ else if ((state->flags & INPUT_COMPLETE_FILENAMES) != 0)
+ {
+ if (state->is_cd)
+ state->flags &= ~(INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_COMMANDS);
+ SHOW_C_CTX ("try_complete:filename_subst_1");
+ matches = completion_matches (state->word, filename_completion_function, state->flags);
+
+ if (matches == NULL && state->is_cd && !IS_PATH_SEP (*state->word) && *state->word != '~')
+ {
+ state->q = text + *lc_start;
+ for (state->p = text;
+ *state->p != '\0' && state->p < state->q && whitespace (*state->p);
+ str_next_char (&state->p))
+ ;
+ if (strncmp (state->p, "cd", 2) == 0)
+ for (state->p += 2;
+ *state->p != '\0' && state->p < state->q && whitespace (*state->p);
+ str_next_char (&state->p))
+ ;
+ if (state->p == state->q)
+ {
+ char *cdpath_ref, *cdpath;
+ char c;
+
+ cdpath_ref = g_strdup (getenv ("CDPATH"));
+ cdpath = cdpath_ref;
+ c = (cdpath == NULL) ? '\0' : ':';
+
+ while (matches == NULL && c == ':')
+ {
+ char *s;
+
+ s = strchr (cdpath, ':');
+ /* cppcheck-suppress nullPointer */
+ if (s == NULL)
+ s = strchr (cdpath, '\0');
+ c = *s;
+ *s = '\0';
+ if (*cdpath != '\0')
+ {
+ state->r = mc_build_filename (cdpath, state->word, (char *) NULL);
+ SHOW_C_CTX ("try_complete:filename_subst_2");
+ matches =
+ completion_matches (state->r, filename_completion_function,
+ state->flags);
+ g_free (state->r);
+ }
+ *s = c;
+ cdpath = str_get_next_char (s);
+ }
+ g_free (cdpath_ref);
+ }
+ }
+ }
+ return matches;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+insert_text (WInput * in, char *text, ssize_t size)
+{
+ size_t text_len;
+ int buff_len;
+ ssize_t new_size;
+
+ text_len = strlen (text);
+ buff_len = str_length (in->buffer->str);
+ if (size < 0)
+ size = (ssize_t) text_len;
+ else
+ size = MIN (size, (ssize_t) text_len);
+
+ new_size = size + start - end;
+ if (new_size != 0)
+ {
+ /* make a hole within buffer */
+
+ size_t tail_len;
+
+ tail_len = in->buffer->len - end;
+ if (tail_len != 0)
+ {
+ char *tail;
+ size_t hole_end;
+
+ tail = g_strndup (in->buffer->str + end, tail_len);
+
+ hole_end = end + new_size;
+ if (in->buffer->len < hole_end)
+ g_string_set_size (in->buffer, hole_end + tail_len);
+
+ g_string_overwrite_len (in->buffer, hole_end, tail, tail_len);
+
+ g_free (tail);
+ }
+ }
+
+ g_string_overwrite_len (in->buffer, start, text, size);
+
+ in->point += str_length (in->buffer->str) - buff_len;
+ input_update (in, TRUE);
+ end += new_size;
+
+ return new_size != 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+complete_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ static int bl = 0;
+
+ WGroup *g = GROUP (w);
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_KEY:
+ switch (parm)
+ {
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ bl = 0;
+ h->ret_value = 0;
+ dlg_stop (h);
+ return MSG_HANDLED;
+
+ case KEY_BACKSPACE:
+ bl = 0;
+ /* exit from completion list if input line is empty */
+ if (end == 0)
+ {
+ h->ret_value = 0;
+ dlg_stop (h);
+ }
+ /* Refill the list box and start again */
+ else if (end == min_end)
+ {
+ end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
+ input_handle_char (input, parm);
+ h->ret_value = B_USER;
+ dlg_stop (h);
+ }
+ else
+ {
+ int new_end;
+ int i;
+ GList *e;
+
+ new_end = str_get_prev_char (input->buffer->str + end) - input->buffer->str;
+
+ for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
+ e != NULL; i++, e = g_list_next (e))
+ {
+ WLEntry *le = LENTRY (e->data);
+
+ if (strncmp (input->buffer->str + start, le->text, new_end - start) == 0)
+ {
+ listbox_select_entry (LISTBOX (g->current->data), i);
+ end = new_end;
+ input_handle_char (input, parm);
+ widget_draw (WIDGET (g->current->data));
+ break;
+ }
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ if (parm < 32 || parm > 255)
+ {
+ bl = 0;
+ if (widget_lookup_key (WIDGET (input), parm) != CK_Complete)
+ return MSG_NOT_HANDLED;
+
+ if (end == min_end)
+ return MSG_HANDLED;
+
+ /* This means we want to refill the list box and start again */
+ h->ret_value = B_USER;
+ dlg_stop (h);
+ }
+ else
+ {
+ static char buff[MB_LEN_MAX] = "";
+ GList *e;
+ int i;
+ int need_redraw = 0;
+ int low = 4096;
+ char *last_text = NULL;
+
+ buff[bl++] = (char) parm;
+ buff[bl] = '\0';
+
+ switch (str_is_valid_char (buff, bl))
+ {
+ case -1:
+ bl = 0;
+ MC_FALLTHROUGH;
+ case -2:
+ return MSG_HANDLED;
+ default:
+ break;
+ }
+
+ for (i = 0, e = listbox_get_first_link (LISTBOX (g->current->data));
+ e != NULL; i++, e = g_list_next (e))
+ {
+ WLEntry *le = LENTRY (e->data);
+
+ if (strncmp (input->buffer->str + start, le->text, end - start) == 0
+ && strncmp (le->text + end - start, buff, bl) == 0)
+ {
+ if (need_redraw == 0)
+ {
+ need_redraw = 1;
+ listbox_select_entry (LISTBOX (g->current->data), i);
+ last_text = le->text;
+ }
+ else
+ {
+ char *si, *sl;
+ int si_num = 0;
+ int sl_num = 0;
+
+ /* count symbols between start and end */
+ for (si = le->text + start; si < le->text + end;
+ str_next_char (&si), si_num++)
+ ;
+ for (sl = last_text + start; sl < last_text + end;
+ str_next_char (&sl), sl_num++)
+ ;
+
+ /* pointers to next symbols */
+ si = &le->text[str_offset_to_pos (le->text, ++si_num)];
+ sl = &last_text[str_offset_to_pos (last_text, ++sl_num)];
+
+ while (si[0] != '\0' && sl[0] != '\0')
+ {
+ char *nexti, *nextl;
+
+ nexti = str_get_next_char (si);
+ nextl = str_get_next_char (sl);
+
+ if (nexti - si != nextl - sl || strncmp (si, sl, nexti - si) != 0)
+ break;
+
+ si = nexti;
+ sl = nextl;
+
+ si_num++;
+ }
+
+ last_text = le->text;
+
+ si = &last_text[str_offset_to_pos (last_text, si_num)];
+ if (low > si - last_text)
+ low = si - last_text;
+
+ need_redraw = 2;
+ }
+ }
+ }
+
+ if (need_redraw == 2)
+ {
+ insert_text (input, last_text, low);
+ widget_draw (WIDGET (g->current->data));
+ }
+ else if (need_redraw == 1)
+ {
+ h->ret_value = B_ENTER;
+ dlg_stop (h);
+ }
+ bl = 0;
+ }
+ }
+ return MSG_HANDLED;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns TRUE if the user would like to see us again */
+static gboolean
+complete_engine (WInput * in, int what_to_do)
+{
+ if (in->completions != NULL && str_offset_to_pos (in->buffer->str, in->point) != end)
+ input_complete_free (in);
+
+ if (in->completions == NULL)
+ complete_engine_fill_completions (in);
+
+ if (in->completions == NULL)
+ tty_beep ();
+ else
+ {
+ if ((what_to_do & DO_INSERTION) != 0
+ || ((what_to_do & DO_QUERY) != 0 && in->completions[1] == NULL))
+ {
+ char *lc_complete = in->completions[0];
+
+ if (!insert_text (in, lc_complete, -1) || in->completions[1] != NULL)
+ tty_beep ();
+ else
+ input_complete_free (in);
+ }
+
+ if ((what_to_do & DO_QUERY) != 0 && in->completions != NULL && in->completions[1] != NULL)
+ {
+ int maxlen = 0, count = 0, i;
+ int x, y, w, h;
+ int start_x, start_y;
+ char **p, *q;
+ WDialog *complete_dlg;
+ WListbox *complete_list;
+
+ for (p = in->completions + 1; *p != NULL; count++, p++)
+ {
+ i = str_term_width1 (*p);
+ if (i > maxlen)
+ maxlen = i;
+ }
+
+ start_x = WIDGET (in)->rect.x;
+ start_y = WIDGET (in)->rect.y;
+ if (start_y - 2 >= count)
+ {
+ y = start_y - 2 - count;
+ h = 2 + count;
+ }
+ else if (start_y >= LINES - start_y - 1)
+ {
+ y = 0;
+ h = start_y;
+ }
+ else
+ {
+ y = start_y + 1;
+ h = LINES - start_y - 1;
+ }
+ x = start - in->term_first_shown - 2 + start_x;
+ w = maxlen + 4;
+ if (x + w > COLS)
+ x = COLS - w;
+ if (x < 0)
+ x = 0;
+ if (x + w > COLS)
+ w = COLS;
+
+ input = in;
+ min_end = end;
+ complete_height = h;
+ complete_width = w;
+
+ complete_dlg =
+ dlg_create (TRUE, y, x, complete_height, complete_width, WPOS_KEEP_DEFAULT, TRUE,
+ dialog_colors, complete_callback, NULL, "[Completion]", NULL);
+ complete_list = listbox_new (1, 1, h - 2, w - 2, FALSE, NULL);
+ group_add_widget (GROUP (complete_dlg), complete_list);
+
+ for (p = in->completions + 1; *p != NULL; p++)
+ listbox_add_item (complete_list, LISTBOX_APPEND_AT_END, 0, *p, NULL, FALSE);
+
+ i = dlg_run (complete_dlg);
+ q = NULL;
+ if (i == B_ENTER)
+ {
+ listbox_get_current (complete_list, &q, NULL);
+ if (q != NULL)
+ insert_text (in, q, -1);
+ }
+ if (q != NULL || end != min_end)
+ input_complete_free (in);
+ widget_destroy (WIDGET (complete_dlg));
+
+ /* B_USER if user wants to start over again */
+ return (i == B_USER);
+ }
+ }
+
+ return FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns an array of matches, or NULL if none. */
+char **
+try_complete (char *text, int *lc_start, int *lc_end, input_complete_t flags)
+{
+ try_complete_automation_state_t state;
+ char **matches = NULL;
+
+ memset (&state, 0, sizeof (state));
+ state.flags = flags;
+
+ SHOW_C_CTX ("try_complete");
+ state.word = g_strndup (text + *lc_start, *lc_end - *lc_start);
+
+ state.is_cd = check_is_cd (text, *lc_start, state.flags);
+
+ /* Determine if this could be a command word. It is if it appears at
+ the start of the line (ignoring preceding whitespace), or if it
+ appears after a character that separates commands. And we have to
+ be in a INPUT_COMPLETE_COMMANDS flagged Input line. */
+ if (!state.is_cd && (flags & INPUT_COMPLETE_COMMANDS) != 0)
+ try_complete_commands_prepare (&state, text, lc_start);
+
+ try_complete_find_start_sign (&state);
+
+ /* Command substitution? */
+ if (state.p > state.q && state.p > state.r)
+ {
+ SHOW_C_CTX ("try_complete:cmd_backq_subst");
+ matches = completion_matches (str_cget_next_char (state.p),
+ command_completion_function,
+ state.flags & (~INPUT_COMPLETE_FILENAMES));
+ if (matches != NULL)
+ *lc_start += str_get_next_char (state.p) - state.word;
+ }
+
+ /* Variable name? */
+ else if (state.q > state.p && state.q > state.r)
+ {
+ SHOW_C_CTX ("try_complete:var_subst");
+ matches = completion_matches (state.q, variable_completion_function, state.flags);
+ if (matches != NULL)
+ *lc_start += state.q - state.word;
+ }
+
+ /* Starts with '@', then look through the known hostnames for
+ completion first. */
+ else if (state.r > state.p && state.r > state.q)
+ {
+ SHOW_C_CTX ("try_complete:host_subst");
+ matches = completion_matches (state.r, hostname_completion_function, state.flags);
+ if (matches != NULL)
+ *lc_start += state.r - state.word;
+ }
+
+ /* Starts with '~' and there is no slash in the word, then
+ try completing this word as a username. */
+ if (matches == NULL && *state.word == '~' && (state.flags & INPUT_COMPLETE_USERNAMES) != 0
+ && strchr (state.word, PATH_SEP) == NULL)
+ {
+ SHOW_C_CTX ("try_complete:user_subst");
+ matches = completion_matches (state.word, username_completion_function, state.flags);
+ }
+
+ /* If this word is in a command position, then
+ complete over possible command names, including aliases, functions,
+ and command names. */
+ if (matches == NULL)
+ matches = try_complete_all_possible (&state, text, lc_start);
+
+ /* And finally if nothing found, try complete directory name */
+ if (matches == NULL)
+ {
+ state.in_command_position = 0;
+ matches = try_complete_all_possible (&state, text, lc_start);
+ }
+
+ g_free (state.word);
+
+ if (matches != NULL &&
+ (flags & (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC)) !=
+ (INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_SHELL_ESC))
+ {
+ /* FIXME: HACK? INPUT_COMPLETE_SHELL_ESC is used only in command line. */
+ char **m;
+
+ for (m = matches; *m != NULL; m++)
+ {
+ char *p;
+
+ p = *m;
+ *m = strutils_shell_escape (*m);
+ g_free (p);
+ }
+ }
+
+ return matches;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+complete_engine_fill_completions (WInput * in)
+{
+ char *s;
+ const char *word_separators;
+
+ word_separators = (in->completion_flags & INPUT_COMPLETE_SHELL_ESC) ? " \t;|<>" : "\t;|<>";
+
+ end = str_offset_to_pos (in->buffer->str, in->point);
+
+ s = in->buffer->str;
+ if (in->point != 0)
+ {
+ /* get symbol before in->point */
+ size_t i;
+
+ for (i = in->point - 1; i > 0; i--)
+ str_next_char (&s);
+ }
+
+ for (; s >= in->buffer->str; str_prev_char (&s))
+ {
+ start = s - in->buffer->str;
+ if (strchr (word_separators, *s) != NULL && !strutils_is_char_escaped (in->buffer->str, s))
+ break;
+ }
+
+ if (start < end)
+ {
+ str_next_char (&s);
+ start = s - in->buffer->str;
+ }
+
+ in->completions = try_complete (in->buffer->str, &start, &end, in->completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* declared in lib/widget/input.h */
+void
+input_complete (WInput * in)
+{
+ int engine_flags;
+
+ if (!str_is_valid_string (in->buffer->str))
+ return;
+
+ if (in->completions != NULL)
+ engine_flags = DO_QUERY;
+ else
+ {
+ engine_flags = DO_INSERTION;
+
+ if (mc_global.widget.show_all_if_ambiguous)
+ engine_flags |= DO_QUERY;
+ }
+
+ while (complete_engine (in, engine_flags))
+ ;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+input_complete_free (WInput * in)
+{
+ g_strfreev (in->completions);
+ in->completions = NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/label.c b/lib/widget/label.c
new file mode 100644
index 0000000..97f82af
--- /dev/null
+++ b/lib/widget/label.c
@@ -0,0 +1,197 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 label.c
+ * \brief Source: WLabel widget
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static cb_ret_t
+label_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WLabel *l = LABEL (w);
+
+ switch (msg)
+ {
+ case MSG_DRAW:
+ {
+ char *p = l->text;
+ int y = 0;
+ gboolean disabled;
+ align_crt_t align;
+
+ if (l->text == NULL)
+ return MSG_HANDLED;
+
+ disabled = widget_get_state (w, WST_DISABLED);
+
+ if (l->transparent)
+ tty_setcolor (disabled ? DISABLED_COLOR : DEFAULT_COLOR);
+ else
+ {
+ const int *colors;
+
+ colors = widget_get_colors (w);
+ tty_setcolor (disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL]);
+ }
+
+ align = (w->pos_flags & WPOS_CENTER_HORZ) != 0 ? J_CENTER_LEFT : J_LEFT;
+
+ while (TRUE)
+ {
+ char *q;
+ char c = '\0';
+
+
+ q = strchr (p, '\n');
+ if (q != NULL)
+ {
+ c = q[0];
+ q[0] = '\0';
+ }
+
+ widget_gotoyx (w, y, 0);
+ tty_print_string (str_fit_to_term (p, w->rect.cols, align));
+
+ if (q == NULL)
+ break;
+
+ q[0] = c;
+ p = q + 1;
+ y++;
+ }
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ g_free (l->text);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WLabel *
+label_new (int y, int x, const char *text)
+{
+ WRect r = { y, x, 1, 1 };
+ WLabel *l;
+ Widget *w;
+
+ if (text != NULL)
+ str_msg_term_size (text, &r.lines, &r.cols);
+
+ l = g_new (WLabel, 1);
+ w = WIDGET (l);
+ widget_init (w, &r, label_callback, NULL);
+
+ l->text = g_strdup (text);
+ l->auto_adjust_cols = TRUE;
+ l->transparent = FALSE;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+label_set_text (WLabel * label, const char *text)
+{
+ Widget *w = WIDGET (label);
+ int newcols = w->rect.cols;
+ int newlines;
+
+ if (label->text != NULL && text != NULL && strcmp (label->text, text) == 0)
+ return; /* Flickering is not nice */
+
+ g_free (label->text);
+
+ if (text == NULL)
+ label->text = NULL;
+ else
+ {
+ label->text = g_strdup (text);
+ if (label->auto_adjust_cols)
+ {
+ str_msg_term_size (text, &newlines, &newcols);
+ w->rect.cols = MAX (newcols, w->rect.cols);
+ w->rect.lines = MAX (newlines, w->rect.lines);
+ }
+ }
+
+ widget_draw (w);
+
+ w->rect.cols = MIN (newcols, w->rect.cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+label_set_textv (WLabel * label, const char *format, ...)
+{
+ va_list args;
+ char buf[BUF_1K]; /* FIXME: is it enough? */
+
+ va_start (args, format);
+ g_vsnprintf (buf, sizeof (buf), format, args);
+ va_end (args);
+
+ label_set_text (label, buf);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/label.h b/lib/widget/label.h
new file mode 100644
index 0000000..6d1607f
--- /dev/null
+++ b/lib/widget/label.h
@@ -0,0 +1,37 @@
+
+/** \file label.h
+ * \brief Header: WLabel widget
+ */
+
+#ifndef MC__WIDGET_LABEL_H
+#define MC__WIDGET_LABEL_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LABEL(x) ((WLabel *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ Widget widget;
+ gboolean auto_adjust_cols; /* compute widget.cols from strlen(text)? */
+ char *text;
+ gboolean transparent; /* Paint in the default color fg/bg */
+} WLabel;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WLabel *label_new (int y, int x, const char *text);
+void label_set_text (WLabel * label, const char *text);
+/* *INDENT-OFF* */
+void label_set_textv (WLabel * label, const char *format, ...) G_GNUC_PRINTF (2, 3);
+/* *INDENT-ON* */
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_LABEL_H */
diff --git a/lib/widget/listbox-window.c b/lib/widget/listbox-window.c
new file mode 100644
index 0000000..42551c4
--- /dev/null
+++ b/lib/widget/listbox-window.c
@@ -0,0 +1,175 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1995
+ Andrew Borodin <aborodin@vmail.ru>, 2009, 2010, 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 listbox-window.c
+ * \brief Source: Listbox widget, a listbox within dialog window
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h" /* COLS */
+#include "lib/skin.h"
+#include "lib/strutil.h" /* str_term_width1() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+Listbox *
+create_listbox_window_centered (int center_y, int center_x, int lines, int cols,
+ const char *title, const char *help)
+{
+ const int space = 4;
+
+ int xpos = 0, ypos = 0;
+ Listbox *listbox;
+ widget_pos_flags_t pos_flags = WPOS_TRYUP;
+
+ /* Adjust sizes */
+ lines = MIN (lines, LINES - 6);
+
+ if (title != NULL)
+ {
+ int len;
+
+ len = str_term_width1 (title) + 4;
+ cols = MAX (cols, len);
+ }
+
+ cols = MIN (cols, COLS - 6);
+
+ /* adjust position */
+ if ((center_y < 0) || (center_x < 0))
+ pos_flags |= WPOS_CENTER;
+ else
+ {
+ /* Actually, this this is not used in MC. */
+
+ ypos = center_y;
+ xpos = center_x;
+
+ ypos -= lines / 2;
+ xpos -= cols / 2;
+
+ if (ypos + lines >= LINES)
+ ypos = LINES - lines - space;
+ if (ypos < 0)
+ ypos = 0;
+
+ if (xpos + cols >= COLS)
+ xpos = COLS - cols - space;
+ if (xpos < 0)
+ xpos = 0;
+ }
+
+ listbox = g_new (Listbox, 1);
+
+ listbox->dlg =
+ dlg_create (TRUE, ypos, xpos, lines + space, cols + space, pos_flags, FALSE, listbox_colors,
+ NULL, NULL, help, title);
+
+ listbox->list = listbox_new (2, 2, lines, cols, FALSE, NULL);
+ group_add_widget (GROUP (listbox->dlg), listbox->list);
+
+ return listbox;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+Listbox *
+create_listbox_window (int lines, int cols, const char *title, const char *help)
+{
+ return create_listbox_window_centered (-1, -1, lines, cols, title, help);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/** Returns the number of the item selected */
+int
+run_listbox (Listbox * l)
+{
+ int val = -1;
+
+ if (dlg_run (l->dlg) != B_CANCEL)
+ val = l->list->pos;
+ widget_destroy (WIDGET (l->dlg));
+ g_free (l);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * A variant of run_listbox() which is more convenient to use when we
+ * need to select arbitrary 'data'.
+ *
+ * @param select the item to select initially, by its 'data'. Optional.
+ * @return the 'data' of the item selected, or NULL if none selected.
+ */
+void *
+run_listbox_with_data (Listbox * l, const void *select)
+{
+ void *val = NULL;
+
+ if (select != NULL)
+ listbox_select_entry (l->list, listbox_search_data (l->list, select));
+
+ if (dlg_run (l->dlg) != B_CANCEL)
+ {
+ WLEntry *e;
+ e = listbox_get_nth_item (l->list, l->list->pos);
+ if (e != NULL)
+ {
+ /* The assert guards against returning a soon-to-be deallocated
+ * pointer (as in listbox_add_item(..., TRUE)). */
+ g_assert (!e->free_data);
+ val = e->data;
+ }
+ }
+
+ widget_destroy (WIDGET (l->dlg));
+ g_free (l);
+ return val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/listbox-window.h b/lib/widget/listbox-window.h
new file mode 100644
index 0000000..5a60829
--- /dev/null
+++ b/lib/widget/listbox-window.h
@@ -0,0 +1,36 @@
+/** \file listbox-window.h
+ * \brief Header: Listbox widget, a listbox within dialog window
+ */
+
+#ifndef MC__LISTBOX_DIALOG_H
+#define MC__LISTBOX_DIALOG_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LISTBOX_APPEND_TEXT(l,h,t,d,f) \
+ listbox_add_item (l->list, LISTBOX_APPEND_AT_END, h, t, d, f)
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct
+{
+ WDialog *dlg;
+ WListbox *list;
+} Listbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Listbox utility functions */
+Listbox *create_listbox_window_centered (int center_y, int center_x, int lines, int cols,
+ const char *title, const char *help);
+Listbox *create_listbox_window (int lines, int cols, const char *title, const char *help);
+int run_listbox (Listbox * l);
+void *run_listbox_with_data (Listbox * l, const void *select);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__LISTBOX_DIALOG_H */
diff --git a/lib/widget/listbox.c b/lib/widget/listbox.c
new file mode 100644
index 0000000..80e8ebf
--- /dev/null
+++ b/lib/widget/listbox.c
@@ -0,0 +1,828 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 listbox.c
+ * \brief Source: WListbox widget
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/util.h" /* Q_() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *listbox_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/* Gives the position of the last item. */
+#define LISTBOX_LAST(l) (listbox_is_empty (l) ? 0 : (int) g_queue_get_length ((l)->list) - 1)
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+static int
+listbox_entry_cmp (const void *a, const void *b, void *user_data)
+{
+ const WLEntry *ea = (const WLEntry *) a;
+ const WLEntry *eb = (const WLEntry *) b;
+
+ (void) user_data;
+
+ return strcmp (ea->text, eb->text);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_entry_free (void *data)
+{
+ WLEntry *e = data;
+
+ g_free (e->text);
+ if (e->free_data)
+ g_free (e->data);
+ g_free (e);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_drawscroll (const WListbox * l)
+{
+ const WRect *w = &CONST_WIDGET (l)->rect;
+ int max_line = w->lines - 1;
+ int line = 0;
+ int i;
+ int length;
+
+ /* Are we at the top? */
+ widget_gotoyx (l, 0, w->cols);
+ if (l->top == 0)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('^');
+
+ length = g_queue_get_length (l->list);
+
+ /* Are we at the bottom? */
+ widget_gotoyx (w, max_line, w->cols);
+ if (l->top + w->lines == length || w->lines >= length)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('v');
+
+ /* Now draw the nice relative pointer */
+ if (!g_queue_is_empty (l->list))
+ line = 1 + ((l->pos * (w->lines - 2)) / length);
+
+ for (i = 1; i < max_line; i++)
+ {
+ widget_gotoyx (l, i, w->cols);
+ if (i != line)
+ tty_print_one_vline (TRUE);
+ else
+ tty_print_char ('*');
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_draw (WListbox * l, gboolean focused)
+{
+ Widget *wl = WIDGET (l);
+ const WRect *w = &CONST_WIDGET (l)->rect;
+ const int *colors;
+ gboolean disabled;
+ int normalc, selc;
+ int length = 0;
+ GList *le = NULL;
+ int pos;
+ int i;
+ int sel_line = -1;
+
+ colors = widget_get_colors (wl);
+
+ disabled = widget_get_state (wl, WST_DISABLED);
+ normalc = disabled ? DISABLED_COLOR : colors[DLG_COLOR_NORMAL];
+ selc = disabled ? DISABLED_COLOR : colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_FOCUS];
+
+ if (l->list != NULL)
+ {
+ length = g_queue_get_length (l->list);
+ le = g_queue_peek_nth_link (l->list, (guint) l->top);
+ }
+
+ /* pos = (le == NULL) ? 0 : g_list_position (l->list, le); */
+ pos = (le == NULL) ? 0 : l->top;
+
+ for (i = 0; i < w->lines; i++)
+ {
+ const char *text = "";
+
+ /* Display the entry */
+ if (pos == l->pos && sel_line == -1)
+ {
+ sel_line = i;
+ tty_setcolor (selc);
+ }
+ else
+ tty_setcolor (normalc);
+
+ widget_gotoyx (l, i, 1);
+
+ if (l->list != NULL && le != NULL && (i == 0 || pos < length))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ text = e->text;
+ le = g_list_next (le);
+ pos++;
+ }
+
+ tty_print_string (str_fit_to_term (text, w->cols - 2, J_LEFT_FIT));
+ }
+
+ l->cursor_y = sel_line;
+
+ if (l->scrollbar && length > w->lines)
+ {
+ tty_setcolor (normalc);
+ listbox_drawscroll (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+listbox_check_hotkey (WListbox * l, int key)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (e->hotkey == key)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Calculates the item displayed at screen row 'y' (y==0 being the widget's 1st row). */
+static int
+listbox_y_pos (WListbox * l, int y)
+{
+ return MIN (l->top + y, LISTBOX_LAST (l));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_fwd (WListbox * l, gboolean wrap)
+{
+ if (!listbox_is_empty (l))
+ {
+ if ((guint) l->pos + 1 < g_queue_get_length (l->list))
+ listbox_select_entry (l, l->pos + 1);
+ else if (wrap)
+ listbox_select_first (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_fwd_n (WListbox * l, int n)
+{
+ listbox_select_entry (l, MIN (l->pos + n, LISTBOX_LAST (l)));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_back (WListbox * l, gboolean wrap)
+{
+ if (!listbox_is_empty (l))
+ {
+ if (l->pos > 0)
+ listbox_select_entry (l, l->pos - 1);
+ else if (wrap)
+ listbox_select_last (l);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_back_n (WListbox * l, int n)
+{
+ listbox_select_entry (l, MAX (l->pos - n, 0));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+listbox_execute_cmd (WListbox * l, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+ const WRect *w = &CONST_WIDGET (l)->rect;
+
+ if (l->list == NULL || g_queue_is_empty (l->list))
+ return MSG_NOT_HANDLED;
+
+ switch (command)
+ {
+ case CK_Up:
+ listbox_back (l, TRUE);
+ break;
+ case CK_Down:
+ listbox_fwd (l, TRUE);
+ break;
+ case CK_Top:
+ listbox_select_first (l);
+ break;
+ case CK_Bottom:
+ listbox_select_last (l);
+ break;
+ case CK_PageUp:
+ listbox_back_n (l, w->lines - 1);
+ break;
+ case CK_PageDown:
+ listbox_fwd_n (l, w->lines - 1);
+ break;
+ case CK_Delete:
+ if (l->deletable)
+ {
+ gboolean is_last, is_more;
+ int length;
+
+ length = g_queue_get_length (l->list);
+
+ is_last = (l->pos + 1 >= length);
+ is_more = (l->top + w->lines >= length);
+
+ listbox_remove_current (l);
+ if ((l->top > 0) && (is_last || is_more))
+ l->top--;
+ }
+ break;
+ case CK_Clear:
+ if (l->deletable && mc_global.widget.confirm_history_cleanup
+ /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
+ && (query_dialog (Q_ ("DialogTitle|History cleanup"),
+ _("Do you want clean this history?"),
+ D_ERROR, 2, _("&Yes"), _("&No")) == 0))
+ listbox_remove_list (l);
+ break;
+ case CK_View:
+ case CK_Edit:
+ case CK_Enter:
+ ret = send_message (WIDGET (l)->owner, l, MSG_NOTIFY, command, NULL);
+ break;
+ default:
+ ret = MSG_NOT_HANDLED;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return MSG_HANDLED if we want a redraw */
+static cb_ret_t
+listbox_key (WListbox * l, int key)
+{
+ long command;
+
+ if (l->list == NULL)
+ return MSG_NOT_HANDLED;
+
+ /* focus on listbox item N by '0'..'9' keys */
+ if (key >= '0' && key <= '9')
+ {
+ listbox_select_entry (l, key - '0');
+ return MSG_HANDLED;
+ }
+
+ command = widget_lookup_key (WIDGET (l), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return listbox_execute_cmd (l, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Listbox item adding function */
+static inline void
+listbox_append_item (WListbox * l, WLEntry * e, listbox_append_t pos)
+{
+ if (l->list == NULL)
+ {
+ l->list = g_queue_new ();
+ pos = LISTBOX_APPEND_AT_END;
+ }
+
+ switch (pos)
+ {
+ case LISTBOX_APPEND_AT_END:
+ g_queue_push_tail (l->list, e);
+ break;
+
+ case LISTBOX_APPEND_BEFORE:
+ g_queue_insert_before (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
+ break;
+
+ case LISTBOX_APPEND_AFTER:
+ g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->pos), e);
+ break;
+
+ case LISTBOX_APPEND_SORTED:
+ g_queue_insert_sorted (l->list, e, (GCompareDataFunc) listbox_entry_cmp, NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Call this whenever the user changes the selected item. */
+static void
+listbox_on_change (WListbox * l)
+{
+ listbox_draw (l, TRUE);
+ send_message (WIDGET (l)->owner, l, MSG_NOTIFY, 0, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_do_action (WListbox * l)
+{
+ int action;
+
+ if (listbox_is_empty (l))
+ return;
+
+ if (l->callback != NULL)
+ action = l->callback (l);
+ else
+ action = LISTBOX_DONE;
+
+ if (action == LISTBOX_DONE)
+ {
+ WDialog *h = DIALOG (WIDGET (l)->owner);
+
+ h->ret_value = B_ENTER;
+ dlg_stop (h);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_run_hotkey (WListbox * l, int pos)
+{
+ listbox_select_entry (l, pos);
+ listbox_on_change (l);
+ listbox_do_action (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+listbox_destroy (WListbox * l)
+{
+ listbox_remove_list (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+listbox_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WListbox *l = LISTBOX (w);
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ {
+ int pos;
+
+ pos = listbox_check_hotkey (l, parm);
+ if (pos < 0)
+ return MSG_NOT_HANDLED;
+
+ listbox_run_hotkey (l, pos);
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_KEY:
+ {
+ cb_ret_t ret_code;
+
+ ret_code = listbox_key (l, parm);
+ if (ret_code != MSG_NOT_HANDLED)
+ listbox_on_change (l);
+ return ret_code;
+ }
+
+ case MSG_ACTION:
+ return listbox_execute_cmd (l, parm);
+
+ case MSG_CURSOR:
+ widget_gotoyx (l, l->cursor_y, 0);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ listbox_draw (l, widget_get_state (w, WST_FOCUSED));
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ listbox_destroy (l);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+listbox_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ WListbox *l = LISTBOX (w);
+ int old_pos;
+
+ old_pos = l->pos;
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ widget_select (w);
+ listbox_select_entry (l, listbox_y_pos (l, event->y));
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ listbox_back (l, FALSE);
+ break;
+
+ case MSG_MOUSE_SCROLL_DOWN:
+ listbox_fwd (l, FALSE);
+ break;
+
+ case MSG_MOUSE_DRAG:
+ event->result.repeat = TRUE; /* It'd be functional even without this. */
+ listbox_select_entry (l, listbox_y_pos (l, event->y));
+ break;
+
+ case MSG_MOUSE_CLICK:
+ /* We don't call listbox_select_entry() here: MSG_MOUSE_DOWN/DRAG did this already. */
+ if (event->count == GPM_DOUBLE) /* Double click */
+ listbox_do_action (l);
+ break;
+
+ default:
+ break;
+ }
+
+ /* If the selection has changed, we redraw the widget and notify the dialog. */
+ if (l->pos != old_pos)
+ listbox_on_change (l);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WListbox *
+listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback)
+{
+ WRect r = { y, x, 1, width };
+ WListbox *l;
+ Widget *w;
+
+ l = g_new (WListbox, 1);
+ w = WIDGET (l);
+ r.lines = height > 0 ? height : 1;
+ widget_init (w, &r, listbox_callback, listbox_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_HOTKEY;
+ w->keymap = listbox_map;
+
+ l->list = NULL;
+ l->top = l->pos = 0;
+ l->deletable = deletable;
+ l->callback = callback;
+ l->allow_duplicates = TRUE;
+ l->scrollbar = !mc_global.tty.slow_terminal;
+
+ return l;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Finds item by its label.
+ */
+int
+listbox_search_text (WListbox * l, const char *text)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (strcmp (e->text, text) == 0)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Finds item by its 'data' slot.
+ */
+int
+listbox_search_data (WListbox * l, const void *data)
+{
+ if (!listbox_is_empty (l))
+ {
+ int i;
+ GList *le;
+
+ for (i = 0, le = g_queue_peek_head_link (l->list); le != NULL; i++, le = g_list_next (le))
+ {
+ WLEntry *e = LENTRY (le->data);
+
+ if (e->data == data)
+ return i;
+ }
+ }
+
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Selects the first entry and scrolls the list to the top */
+void
+listbox_select_first (WListbox * l)
+{
+ l->pos = l->top = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Selects the last entry and scrolls the list to the bottom */
+void
+listbox_select_last (WListbox * l)
+{
+ int lines = WIDGET (l)->rect.lines;
+ int length;
+
+ length = listbox_get_length (l);
+
+ l->pos = DOZ (length, 1);
+ l->top = DOZ (length, lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_select_entry (WListbox * l, int dest)
+{
+ GList *le;
+ int pos;
+ gboolean top_seen = FALSE;
+
+ if (listbox_is_empty (l) || dest < 0)
+ return;
+
+ /* Special case */
+ for (pos = 0, le = g_queue_peek_head_link (l->list); le != NULL; pos++, le = g_list_next (le))
+ {
+ if (pos == l->top)
+ top_seen = TRUE;
+
+ if (pos == dest)
+ {
+ l->pos = dest;
+ if (!top_seen)
+ l->top = l->pos;
+ else
+ {
+ int lines = WIDGET (l)->rect.lines;
+
+ if (l->pos - l->top >= lines)
+ l->top = l->pos - lines + 1;
+ }
+ return;
+ }
+ }
+
+ /* If we are unable to find it, set decent values */
+ l->pos = l->top = 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+listbox_get_length (const WListbox * l)
+{
+ return listbox_is_empty (l) ? 0 : (int) g_queue_get_length (l->list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Returns the current string text as well as the associated extra data */
+void
+listbox_get_current (WListbox * l, char **string, void **extra)
+{
+ WLEntry *e = NULL;
+ gboolean ok;
+
+ if (l != NULL)
+ e = listbox_get_nth_item (l, l->pos);
+
+ ok = (e != NULL);
+
+ if (string != NULL)
+ *string = ok ? e->text : NULL;
+
+ if (extra != NULL)
+ *extra = ok ? e->data : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WLEntry *
+listbox_get_nth_item (const WListbox * l, int pos)
+{
+ if (!listbox_is_empty (l) && pos >= 0)
+ {
+ GList *item;
+
+ item = g_queue_peek_nth_link (l->list, (guint) pos);
+ if (item != NULL)
+ return LENTRY (item->data);
+ }
+
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+GList *
+listbox_get_first_link (const WListbox * l)
+{
+ return (l == NULL || l->list == NULL) ? NULL : g_queue_peek_head_link (l->list);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_remove_current (WListbox * l)
+{
+ if (!listbox_is_empty (l))
+ {
+ GList *current;
+ int length;
+
+ current = g_queue_peek_nth_link (l->list, (guint) l->pos);
+ listbox_entry_free (current->data);
+ g_queue_delete_link (l->list, current);
+
+ length = g_queue_get_length (l->list);
+
+ if (length == 0)
+ l->top = l->pos = 0;
+ else if (l->pos >= length)
+ l->pos = length - 1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+listbox_is_empty (const WListbox * l)
+{
+ return (l == NULL || l->list == NULL || g_queue_is_empty (l->list));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Set new listbox items list.
+ *
+ * @param l WListbox object
+ * @param list list of WLEntry objects
+ */
+void
+listbox_set_list (WListbox * l, GQueue * list)
+{
+ listbox_remove_list (l);
+
+ if (l != NULL)
+ l->list = list;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+listbox_remove_list (WListbox * l)
+{
+ if (l != NULL)
+ {
+ if (l->list != NULL)
+ {
+ g_queue_free_full (l->list, (GDestroyNotify) listbox_entry_free);
+ l->list = NULL;
+ }
+
+ l->pos = l->top = 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text, void *data,
+ gboolean free_data)
+{
+ WLEntry *entry;
+
+ if (l == NULL)
+ return NULL;
+
+ if (!l->allow_duplicates && (listbox_search_text (l, text) >= 0))
+ return NULL;
+
+ entry = g_new (WLEntry, 1);
+ entry->text = g_strdup (text);
+ entry->data = data;
+ entry->free_data = free_data;
+ entry->hotkey = hotkey;
+
+ listbox_append_item (l, entry, pos);
+
+ return entry->text;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/listbox.h b/lib/widget/listbox.h
new file mode 100644
index 0000000..8b2236e
--- /dev/null
+++ b/lib/widget/listbox.h
@@ -0,0 +1,82 @@
+
+/** \file listbox.h
+ * \brief Header: WListbox widget
+ */
+
+#ifndef MC__WIDGET_LISTBOX_H
+#define MC__WIDGET_LISTBOX_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define LISTBOX(x) ((WListbox *)(x))
+#define LENTRY(x) ((WLEntry *)(x))
+
+/*** enums ***************************************************************************************/
+
+/* callback should return one of the following values */
+typedef enum
+{
+ LISTBOX_CONT, /* continue */
+ LISTBOX_DONE /* finish dialog */
+} lcback_ret_t;
+
+typedef enum
+{
+ LISTBOX_APPEND_AT_END = 0, /* append at the end */
+ LISTBOX_APPEND_BEFORE, /* insert before current */
+ LISTBOX_APPEND_AFTER, /* insert after current */
+ LISTBOX_APPEND_SORTED /* insert alphabetically */
+} listbox_append_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct WListbox;
+typedef lcback_ret_t (*lcback_fn) (struct WListbox * l);
+
+typedef struct WLEntry
+{
+ char *text; /* Text to display */
+ int hotkey;
+ void *data; /* Client information */
+ gboolean free_data; /* Whether to free the data on entry's removal */
+} WLEntry;
+
+typedef struct WListbox
+{
+ Widget widget;
+ GQueue *list; /* Pointer to the list of WLEntry */
+ int pos; /* The current element displayed */
+ int top; /* The first element displayed */
+ gboolean allow_duplicates; /* Do we allow duplicates on the list? */
+ gboolean scrollbar; /* Draw a scrollbar? */
+ gboolean deletable; /* Can list entries be deleted? */
+ lcback_fn callback; /* The callback function */
+ int cursor_x, cursor_y; /* Cache the values */
+} WListbox;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *listbox_map;
+
+/*** declarations of public functions ************************************************************/
+
+WListbox *listbox_new (int y, int x, int height, int width, gboolean deletable, lcback_fn callback);
+int listbox_search_text (WListbox * l, const char *text);
+int listbox_search_data (WListbox * l, const void *data);
+void listbox_select_first (WListbox * l);
+void listbox_select_last (WListbox * l);
+void listbox_select_entry (WListbox * l, int dest);
+int listbox_get_length (const WListbox * l);
+void listbox_get_current (WListbox * l, char **string, void **extra);
+WLEntry *listbox_get_nth_item (const WListbox * l, int pos);
+GList *listbox_get_first_link (const WListbox * l);
+void listbox_remove_current (WListbox * l);
+gboolean listbox_is_empty (const WListbox * l);
+void listbox_set_list (WListbox * l, GQueue * list);
+void listbox_remove_list (WListbox * l);
+char *listbox_add_item (WListbox * l, listbox_append_t pos, int hotkey, const char *text,
+ void *data, gboolean free_data);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_LISTBOX_H */
diff --git a/lib/widget/menu.c b/lib/widget/menu.c
new file mode 100644
index 0000000..32ee74c
--- /dev/null
+++ b/lib/widget/menu.c
@@ -0,0 +1,1092 @@
+/*
+ Pulldown menu code
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2012-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 menu.c
+ * \brief Source: pulldown menu code
+ */
+
+#include <config.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/skin.h"
+#include "lib/tty/key.h" /* key macros */
+#include "lib/strutil.h"
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *menu_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+#define MENUENTRY(x) ((menu_entry_t *)(x))
+#define MENU(x) ((menu_t *)(x))
+
+/*** file scope type declarations ****************************************************************/
+
+struct menu_entry_t
+{
+ unsigned char first_letter;
+ hotkey_t text;
+ long command;
+ char *shortcut;
+};
+
+struct menu_t
+{
+ int start_x; /* position relative to menubar start */
+ hotkey_t text;
+ GList *entries;
+ size_t max_entry_len; /* cached max length of entry texts (text + shortcut) */
+ size_t max_hotkey_len; /* cached max length of shortcuts */
+ unsigned int selected; /* pointer to current menu entry */
+ char *help_node;
+};
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menu_arrange (menu_t * menu, dlg_shortcut_str get_shortcut)
+{
+ if (menu != NULL)
+ {
+ GList *i;
+ size_t max_shortcut_len = 0;
+
+ menu->max_entry_len = 1;
+ menu->max_hotkey_len = 1;
+
+ for (i = menu->entries; i != NULL; i = g_list_next (i))
+ {
+ menu_entry_t *entry = MENUENTRY (i->data);
+
+ if (entry != NULL)
+ {
+ size_t len;
+
+ len = (size_t) hotkey_width (entry->text);
+ menu->max_hotkey_len = MAX (menu->max_hotkey_len, len);
+
+ if (get_shortcut != NULL)
+ entry->shortcut = get_shortcut (entry->command);
+
+ if (entry->shortcut != NULL)
+ {
+ len = (size_t) str_term_width1 (entry->shortcut);
+ max_shortcut_len = MAX (max_shortcut_len, len);
+ }
+ }
+ }
+
+ menu->max_entry_len = menu->max_hotkey_len + max_shortcut_len;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_paint_idx (const WMenuBar * menubar, unsigned int idx, int color)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, idx));
+ const int y = 2 + idx;
+ int x = menu->start_x;
+
+ if (x + menu->max_entry_len + 4 > (gsize) w->cols)
+ x = w->cols - menu->max_entry_len - 4;
+
+ if (entry == NULL)
+ {
+ /* menu separator */
+ tty_setcolor (MENU_ENTRY_COLOR);
+
+ widget_gotoyx (menubar, y, x - 1);
+ tty_print_alt_char (ACS_LTEE, FALSE);
+ tty_draw_hline (w->y + y, w->x + x, ACS_HLINE, menu->max_entry_len + 3);
+ widget_gotoyx (menubar, y, x + menu->max_entry_len + 3);
+ tty_print_alt_char (ACS_RTEE, FALSE);
+ }
+ else
+ {
+ int yt, xt;
+
+ /* menu text */
+ tty_setcolor (color);
+ widget_gotoyx (menubar, y, x);
+ tty_print_char ((unsigned char) entry->first_letter);
+ tty_getyx (&yt, &xt);
+ tty_draw_hline (yt, xt, ' ', menu->max_entry_len + 2); /* clear line */
+ tty_print_string (entry->text.start);
+
+ if (entry->text.hotkey != NULL)
+ {
+ tty_setcolor (color == MENU_SELECTED_COLOR ? MENU_HOTSEL_COLOR : MENU_HOT_COLOR);
+ tty_print_string (entry->text.hotkey);
+ tty_setcolor (color);
+ }
+
+ if (entry->text.end != NULL)
+ tty_print_string (entry->text.end);
+
+ if (entry->shortcut != NULL)
+ {
+ widget_gotoyx (menubar, y, x + menu->max_hotkey_len + 3);
+ tty_print_string (entry->shortcut);
+ }
+
+ /* move cursor to the start of entry text */
+ widget_gotoyx (menubar, y, x + 1);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_draw_drop (const WMenuBar * menubar)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const unsigned int count = g_list_length (menu->entries);
+ int column = menu->start_x - 1;
+ unsigned int i;
+
+ if (column + menu->max_entry_len + 5 > (gsize) w->cols)
+ column = w->cols - menu->max_entry_len - 5;
+
+ if (mc_global.tty.shadows)
+ tty_draw_box_shadow (w->y + 1, w->x + column, count + 2, menu->max_entry_len + 5,
+ SHADOW_COLOR);
+
+ tty_setcolor (MENU_ENTRY_COLOR);
+ tty_draw_box (w->y + 1, w->x + column, count + 2, menu->max_entry_len + 5, FALSE);
+
+ for (i = 0; i < count; i++)
+ menubar_paint_idx (menubar, i,
+ i == menu->selected ? MENU_SELECTED_COLOR : MENU_ENTRY_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_set_color (const WMenuBar * menubar, gboolean current, gboolean hotkey)
+{
+ if (!widget_get_state (CONST_WIDGET (menubar), WST_FOCUSED))
+ tty_setcolor (MENU_INACTIVE_COLOR);
+ else if (current)
+ tty_setcolor (hotkey ? MENU_HOTSEL_COLOR : MENU_SELECTED_COLOR);
+ else
+ tty_setcolor (hotkey ? MENU_HOT_COLOR : MENU_ENTRY_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_draw (const WMenuBar * menubar)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ GList *i;
+
+ /* First draw the complete menubar */
+ tty_setcolor (widget_get_state (WIDGET (menubar), WST_FOCUSED) ? MENU_ENTRY_COLOR :
+ MENU_INACTIVE_COLOR);
+ tty_draw_hline (w->y, w->x, ' ', w->cols);
+
+ /* Now each one of the entries */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+ gboolean is_selected = (menubar->selected == (gsize) g_list_position (menubar->menu, i));
+
+ menubar_set_color (menubar, is_selected, FALSE);
+ widget_gotoyx (menubar, 0, menu->start_x);
+
+ tty_print_char (' ');
+ tty_print_string (menu->text.start);
+
+ if (menu->text.hotkey != NULL)
+ {
+ menubar_set_color (menubar, is_selected, TRUE);
+ tty_print_string (menu->text.hotkey);
+ menubar_set_color (menubar, is_selected, FALSE);
+ }
+
+ if (menu->text.end != NULL)
+ tty_print_string (menu->text.end);
+
+ tty_print_char (' ');
+ }
+
+ if (menubar->is_dropped)
+ menubar_draw_drop (menubar);
+ else
+ widget_gotoyx (menubar, 0,
+ MENU (g_list_nth_data (menubar->menu, menubar->selected))->start_x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_remove (WMenuBar * menubar)
+{
+ Widget *g;
+
+ if (!menubar->is_dropped)
+ return;
+
+ /* HACK: before refresh the dialog, change the current widget to keep the order
+ of overlapped widgets. This is useful in multi-window editor.
+ In general, menubar should be a special object, not an ordinary widget
+ in the current dialog. */
+ g = WIDGET (WIDGET (menubar)->owner);
+ GROUP (g)->current = widget_find (g, widget_find_by_id (g, menubar->previous_widget));
+
+ menubar->is_dropped = FALSE;
+ do_refresh ();
+ menubar->is_dropped = TRUE;
+
+ /* restore current widget */
+ GROUP (g)->current = widget_find (g, WIDGET (menubar));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_left (WMenuBar * menubar)
+{
+ menubar_remove (menubar);
+ if (menubar->selected == 0)
+ menubar->selected = g_list_length (menubar->menu) - 1;
+ else
+ menubar->selected--;
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_right (WMenuBar * menubar)
+{
+ menubar_remove (menubar);
+ menubar->selected = (menubar->selected + 1) % g_list_length (menubar->menu);
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_finish (WMenuBar * menubar)
+{
+ Widget *w = WIDGET (menubar);
+
+ menubar->is_dropped = FALSE;
+ w->rect.lines = 1;
+ widget_want_hotkey (w, FALSE);
+ widget_set_options (w, WOP_SELECTABLE, FALSE);
+
+ if (!mc_global.keybar_visible)
+ widget_hide (w);
+ else
+ {
+ /* Move the menubar to the bottom so that widgets displayed on top of
+ * an "invisible" menubar get the first chance to respond to mouse events. */
+ widget_set_bottom (w);
+ }
+
+ /* background must be bottom */
+ if (DIALOG (w->owner)->bg != NULL)
+ widget_set_bottom (WIDGET (DIALOG (w->owner)->bg));
+
+ group_select_widget_by_id (w->owner, menubar->previous_widget);
+ do_refresh ();
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_drop (WMenuBar * menubar, unsigned int selected)
+{
+ menubar->is_dropped = TRUE;
+ menubar->selected = selected;
+ menubar_draw (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_execute (WMenuBar * menubar)
+{
+ const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
+
+ if ((entry != NULL) && (entry->command != CK_IgnoreKey))
+ {
+ Widget *w = WIDGET (menubar);
+
+ mc_global.widget.is_right = (menubar->selected != 0);
+ menubar_finish (menubar);
+ send_message (w->owner, w, MSG_ACTION, entry->command, NULL);
+ do_refresh ();
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_down (WMenuBar * menubar)
+{
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
+
+ do
+ {
+ menu->selected = (menu->selected + 1) % len;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_up (WMenuBar * menubar)
+{
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
+
+ do
+ {
+ if (menu->selected == 0)
+ menu->selected = len - 1;
+ else
+ menu->selected--;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_first (WMenuBar * menubar)
+{
+ if (menubar->is_dropped)
+ {
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+
+ if (menu->selected == 0)
+ return;
+
+ menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
+
+ menu->selected = 0;
+
+ while (TRUE)
+ {
+ menu_entry_t *entry;
+
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
+
+ if ((entry == NULL) || (entry->command == CK_IgnoreKey))
+ menu->selected++;
+ else
+ break;
+ }
+
+ menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
+ }
+ else
+ {
+ menubar->selected = 0;
+ menubar_draw (menubar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_last (WMenuBar * menubar)
+{
+ if (menubar->is_dropped)
+ {
+ menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ const unsigned int len = g_list_length (menu->entries);
+ menu_entry_t *entry;
+
+ if (menu->selected == len - 1)
+ return;
+
+ menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
+
+ menu->selected = len;
+
+ do
+ {
+ menu->selected--;
+ entry = MENUENTRY (g_list_nth_data (menu->entries, menu->selected));
+ }
+ while ((entry == NULL) || (entry->command == CK_IgnoreKey));
+
+ menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
+ }
+ else
+ {
+ menubar->selected = g_list_length (menubar->menu) - 1;
+ menubar_draw (menubar);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_try_drop_menu (WMenuBar * menubar, int hotkey)
+{
+ GList *i;
+
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+
+ if (menu->text.hotkey != NULL && hotkey == g_ascii_tolower (menu->text.hotkey[0]))
+ {
+ menubar_drop (menubar, g_list_position (menubar->menu, i));
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_try_exec_menu (WMenuBar * menubar, int hotkey)
+{
+ menu_t *menu;
+ GList *i;
+
+ menu = g_list_nth_data (menubar->menu, menubar->selected);
+
+ for (i = menu->entries; i != NULL; i = g_list_next (i))
+ {
+ const menu_entry_t *entry = MENUENTRY (i->data);
+
+ if (entry != NULL && entry->text.hotkey != NULL
+ && hotkey == g_ascii_tolower (entry->text.hotkey[0]))
+ {
+ menu->selected = g_list_position (menu->entries, i);
+ menubar_execute (menubar);
+ return MSG_HANDLED;
+ }
+ }
+
+ return MSG_NOT_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_execute_cmd (WMenuBar * menubar, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+
+ switch (command)
+ {
+ case CK_Help:
+ {
+ ev_help_t event_data = { NULL, NULL };
+
+ if (menubar->is_dropped)
+ event_data.node =
+ MENU (g_list_nth_data (menubar->menu, menubar->selected))->help_node;
+ else
+ event_data.node = "[Menu Bar]";
+
+ mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
+ menubar_draw (menubar);
+ }
+ break;
+
+ case CK_Left:
+ menubar_left (menubar);
+ break;
+ case CK_Right:
+ menubar_right (menubar);
+ break;
+ case CK_Up:
+ if (menubar->is_dropped)
+ menubar_up (menubar);
+ break;
+ case CK_Down:
+ if (menubar->is_dropped)
+ menubar_down (menubar);
+ else
+ menubar_drop (menubar, menubar->selected);
+ break;
+ case CK_Home:
+ menubar_first (menubar);
+ break;
+ case CK_End:
+ menubar_last (menubar);
+ break;
+
+ case CK_Enter:
+ if (menubar->is_dropped)
+ menubar_execute (menubar);
+ else
+ menubar_drop (menubar, menubar->selected);
+ break;
+ case CK_Quit:
+ menubar_finish (menubar);
+ break;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+menubar_handle_key (WMenuBar * menubar, int key)
+{
+ long cmd;
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ cmd = widget_lookup_key (WIDGET (menubar), key);
+
+ if (cmd != CK_IgnoreKey)
+ ret = menubar_execute_cmd (menubar, cmd);
+
+ if (ret != MSG_HANDLED)
+ {
+ if (menubar->is_dropped)
+ ret = menubar_try_exec_menu (menubar, key);
+ else
+ ret = menubar_try_drop_menu (menubar, key);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+menubar_refresh (WMenuBar * menubar)
+{
+ Widget *w = WIDGET (menubar);
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ return FALSE;
+
+ /* Trick to get all the mouse events */
+ w->rect.lines = LINES;
+
+ /* Trick to get all of the hotkeys */
+ widget_want_hotkey (w, TRUE);
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+menubar_free_menu (WMenuBar * menubar)
+{
+ g_clear_list (&menubar->menu, (GDestroyNotify) destroy_menu);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+menubar_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WMenuBar *menubar = MENUBAR (w);
+
+ switch (msg)
+ {
+ /* We do not want the focus unless we have been activated */
+ case MSG_FOCUS:
+ if (menubar_refresh (menubar))
+ {
+ menubar_draw (menubar);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_UNFOCUS:
+ return widget_get_state (w, WST_FOCUSED) ? MSG_NOT_HANDLED : MSG_HANDLED;
+
+ /* We don't want the buttonbar to activate while using the menubar */
+ case MSG_HOTKEY:
+ case MSG_KEY:
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ menubar_handle_key (menubar, parm);
+ return MSG_HANDLED;
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_CURSOR:
+ /* Put the cursor in a suitable place */
+ return MSG_NOT_HANDLED;
+
+ case MSG_DRAW:
+ if (widget_get_state (w, WST_VISIBLE) || menubar_refresh (menubar))
+ menubar_draw (menubar);
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ /* try show menu after screen resize */
+ widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
+ menubar_refresh (menubar);
+ return MSG_HANDLED;
+
+ case MSG_DESTROY:
+ menubar_free_menu (menubar);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static unsigned int
+menubar_get_menu_by_x_coord (const WMenuBar * menubar, int x)
+{
+ unsigned int i;
+ GList *menu;
+
+ for (i = 0, menu = menubar->menu;
+ menu != NULL && x >= MENU (menu->data)->start_x; i++, menu = g_list_next (menu))
+ ;
+
+ /* Don't set the invalid value -1 */
+ if (i != 0)
+ i--;
+
+ return i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+menubar_mouse_on_menu (const WMenuBar * menubar, int y, int x)
+{
+ const WRect *w = &CONST_WIDGET (menubar)->rect;
+ menu_t *menu;
+ int left_x, right_x, bottom_y;
+
+ if (!menubar->is_dropped)
+ return FALSE;
+
+ menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ left_x = menu->start_x;
+ right_x = left_x + menu->max_entry_len + 3;
+ if (right_x > w->cols)
+ {
+ left_x = w->cols - (menu->max_entry_len + 3);
+ right_x = w->cols;
+ }
+
+ bottom_y = g_list_length (menu->entries) + 2; /* skip bar and top frame */
+
+ return (x >= left_x && x < right_x && y > 1 && y < bottom_y);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_change_selected_item (WMenuBar * menubar, int y)
+{
+ menu_t *menu;
+ menu_entry_t *entry;
+
+ y -= 2; /* skip bar and top frame */
+ menu = MENU (g_list_nth_data (menubar->menu, menubar->selected));
+ entry = MENUENTRY (g_list_nth_data (menu->entries, y));
+
+ if (entry != NULL && entry->command != CK_IgnoreKey)
+ {
+ menubar_paint_idx (menubar, menu->selected, MENU_ENTRY_COLOR);
+ menu->selected = y;
+ menubar_paint_idx (menubar, menu->selected, MENU_SELECTED_COLOR);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+menubar_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ static gboolean was_drag = FALSE;
+
+ WMenuBar *menubar = MENUBAR (w);
+ gboolean mouse_on_drop;
+
+ mouse_on_drop = menubar_mouse_on_menu (menubar, event->y, event->x);
+
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ was_drag = FALSE;
+
+ if (event->y == 0)
+ {
+ /* events on menubar */
+ unsigned int selected;
+
+ selected = menubar_get_menu_by_x_coord (menubar, event->x);
+ menubar_activate (menubar, TRUE, selected);
+ menubar_remove (menubar); /* if already shown */
+ menubar_drop (menubar, selected);
+ }
+ else if (mouse_on_drop)
+ menubar_change_selected_item (menubar, event->y);
+ else
+ {
+ /* mouse click outside menubar or dropdown -- close menu */
+ menubar_finish (menubar);
+
+ /*
+ * @FIXME.
+ *
+ * Unless we clear the 'capture' flag, we'll receive MSG_MOUSE_DRAG
+ * events belonging to this click (in case the user drags the mouse,
+ * of course).
+ *
+ * For the time being, we mark this with FIXME as this flag should
+ * preferably be regarded as "implementation detail" and not be
+ * touched by us. We should think of some other way of communicating
+ * this to the system.
+ */
+ w->mouse.capture = FALSE;
+ }
+ break;
+
+ case MSG_MOUSE_UP:
+ if (was_drag && mouse_on_drop)
+ menubar_execute (menubar);
+ was_drag = FALSE;
+ break;
+
+ case MSG_MOUSE_CLICK:
+ was_drag = FALSE;
+
+ if ((event->buttons & GPM_B_MIDDLE) != 0 && event->y > 0 && menubar->is_dropped)
+ {
+ /* middle click -- everywhere */
+ menubar_execute (menubar);
+ }
+ else if (mouse_on_drop)
+ menubar_execute (menubar);
+ else if (event->y > 0)
+ /* releasing the mouse button outside the menu -- close menu */
+ menubar_finish (menubar);
+ break;
+
+ case MSG_MOUSE_DRAG:
+ if (event->y == 0)
+ {
+ menubar_remove (menubar);
+ menubar_drop (menubar, menubar_get_menu_by_x_coord (menubar, event->x));
+ }
+ else if (mouse_on_drop)
+ menubar_change_selected_item (menubar, event->y);
+
+ was_drag = TRUE;
+ break;
+
+ case MSG_MOUSE_SCROLL_UP:
+ case MSG_MOUSE_SCROLL_DOWN:
+ was_drag = FALSE;
+
+ if (widget_get_state (w, WST_FOCUSED))
+ {
+ if (event->y == 0)
+ {
+ /* menubar: left/right */
+ if (msg == MSG_MOUSE_SCROLL_UP)
+ menubar_left (menubar);
+ else
+ menubar_right (menubar);
+ }
+ else if (mouse_on_drop)
+ {
+ /* drop-down menu: up/down */
+ if (msg == MSG_MOUSE_SCROLL_UP)
+ menubar_up (menubar);
+ else
+ menubar_down (menubar);
+ }
+ }
+ break;
+
+ default:
+ was_drag = FALSE;
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+menu_entry_t *
+menu_entry_create (const char *name, long command)
+{
+ menu_entry_t *entry;
+
+ entry = g_new (menu_entry_t, 1);
+ entry->first_letter = ' ';
+ entry->text = hotkey_new (name);
+ entry->command = command;
+ entry->shortcut = NULL;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menu_entry_free (menu_entry_t * entry)
+{
+ if (entry != NULL)
+ {
+ hotkey_free (entry->text);
+ g_free (entry->shortcut);
+ g_free (entry);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+menu_t *
+create_menu (const char *name, GList * entries, const char *help_node)
+{
+ menu_t *menu;
+
+ menu = g_new (menu_t, 1);
+ menu->start_x = 0;
+ menu->text = hotkey_new (name);
+ menu->entries = entries;
+ menu->max_entry_len = 1;
+ menu->max_hotkey_len = 0;
+ menu->selected = 0;
+ menu->help_node = g_strdup (help_node);
+
+ return menu;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menu_set_name (menu_t * menu, const char *name)
+{
+ hotkey_free (menu->text);
+ menu->text = hotkey_new (name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+destroy_menu (menu_t * menu)
+{
+ hotkey_free (menu->text);
+ g_list_free_full (menu->entries, (GDestroyNotify) menu_entry_free);
+ g_free (menu->help_node);
+ g_free (menu);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+WMenuBar *
+menubar_new (GList * menu)
+{
+ WRect r = { 0, 0, 1, COLS };
+ WMenuBar *menubar;
+ Widget *w;
+
+ menubar = g_new0 (WMenuBar, 1);
+ w = WIDGET (menubar);
+ widget_init (w, &r, menubar_callback, menubar_mouse_callback);
+ w->pos_flags = WPOS_KEEP_HORZ | WPOS_KEEP_TOP;
+ /* initially, menubar is not selectable */
+ widget_set_options (w, WOP_SELECTABLE, FALSE);
+ w->options |= WOP_TOP_SELECT;
+ w->keymap = menu_map;
+ menubar_set_menu (menubar, menu);
+
+ return menubar;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menubar_set_menu (WMenuBar * menubar, GList * menu)
+{
+ /* delete previous menu */
+ menubar_free_menu (menubar);
+ /* add new menu */
+ menubar->is_dropped = FALSE;
+ menubar->menu = menu;
+ menubar->selected = 0;
+ menubar_arrange (menubar);
+ widget_set_state (WIDGET (menubar), WST_FOCUSED, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+menubar_add_menu (WMenuBar * menubar, menu_t * menu)
+{
+ if (menu != NULL)
+ {
+ menu_arrange (menu, DIALOG (WIDGET (menubar)->owner)->get_shortcut);
+ menubar->menu = g_list_append (menubar->menu, menu);
+ }
+
+ menubar_arrange (menubar);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Properly space menubar items. Should be called when menubar is created
+ * and also when widget width is changed (i.e. upon xterm resize).
+ */
+
+void
+menubar_arrange (WMenuBar * menubar)
+{
+ int start_x = 1;
+ GList *i;
+ int gap;
+
+ if (menubar->menu == NULL)
+ return;
+
+ gap = WIDGET (menubar)->rect.cols - 2;
+
+ /* First, calculate gap between items... */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+
+ /* preserve length here, to be used below */
+ menu->start_x = hotkey_width (menu->text) + 2;
+ gap -= menu->start_x;
+ }
+
+ if (g_list_next (menubar->menu) == NULL)
+ gap = 1;
+ else
+ gap /= (g_list_length (menubar->menu) - 1);
+
+ if (gap <= 0)
+ {
+ /* We are out of luck - window is too narrow... */
+ gap = 1;
+ }
+ else if (gap >= 3)
+ gap = 3;
+
+ /* ...and now fix start positions of menubar items */
+ for (i = menubar->menu; i != NULL; i = g_list_next (i))
+ {
+ menu_t *menu = MENU (i->data);
+ int len = menu->start_x;
+
+ menu->start_x = start_x;
+ start_x += len + gap;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find MenuBar widget in the dialog */
+
+WMenuBar *
+find_menubar (const WDialog * h)
+{
+ return MENUBAR (widget_find_by_type (CONST_WIDGET (h), menubar_callback));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Activate menu bar.
+ *
+ * @param menubar menu bar object
+ * @param dropped whether dropdown menus should be drooped or not
+ * @which number of active dropdown menu
+ */
+void
+menubar_activate (WMenuBar * menubar, gboolean dropped, int which)
+{
+ Widget *w = WIDGET (menubar);
+
+ widget_show (w);
+
+ if (!widget_get_state (w, WST_FOCUSED))
+ {
+ widget_set_options (w, WOP_SELECTABLE, TRUE);
+
+ menubar->is_dropped = dropped;
+ if (which >= 0)
+ menubar->selected = (guint) which;
+
+ menubar->previous_widget = group_get_current_widget_id (w->owner);
+
+ /* Bring it to the top so it receives all mouse events before any other widget.
+ * See also comment in menubar_finish(). */
+ widget_select (w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/menu.h b/lib/widget/menu.h
new file mode 100644
index 0000000..3fc241c
--- /dev/null
+++ b/lib/widget/menu.h
@@ -0,0 +1,63 @@
+/*
+ Header file for pulldown menu engine for Midnignt Commander
+ */
+
+/** \file menu.h
+ * \brief Header: pulldown menu code
+ */
+
+#ifndef MC__WIDGET_MENU_H
+#define MC__WIDGET_MENU_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define MENUBAR(x) ((WMenuBar *)(x))
+
+#define menu_separator_create() NULL
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+struct menu_entry_t;
+typedef struct menu_entry_t menu_entry_t;
+
+struct menu_t;
+typedef struct menu_t menu_t;
+
+/* The button bar menu */
+typedef struct WMenuBar
+{
+ Widget widget;
+
+ gboolean is_dropped; /* If the menubar has dropped */
+ GList *menu; /* The actual menus */
+ guint selected; /* Selected menu on the top bar */
+ unsigned long previous_widget; /* Selected widget ID before activating menu */
+} WMenuBar;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *menu_map;
+
+/*** declarations of public functions ************************************************************/
+
+menu_entry_t *menu_entry_create (const char *name, long command);
+void menu_entry_free (menu_entry_t * me);
+
+menu_t *create_menu (const char *name, GList * entries, const char *help_node);
+void menu_set_name (menu_t * menu, const char *name);
+void destroy_menu (menu_t * menu);
+
+WMenuBar *menubar_new (GList * menu);
+void menubar_set_menu (WMenuBar * menubar, GList * menu);
+void menubar_add_menu (WMenuBar * menubar, menu_t * menu);
+void menubar_arrange (WMenuBar * menubar);
+
+WMenuBar *find_menubar (const WDialog * h);
+
+void menubar_activate (WMenuBar * menubar, gboolean dropped, int which);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_MENU_H */
diff --git a/lib/widget/mouse.c b/lib/widget/mouse.c
new file mode 100644
index 0000000..0c573e8
--- /dev/null
+++ b/lib/widget/mouse.c
@@ -0,0 +1,225 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 2016-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Human beings.
+
+ 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 mouse.c
+ * \brief Header: High-level mouse API
+ */
+
+#include <config.h>
+
+#include "lib/global.h"
+#include "lib/widget.h"
+
+#include "lib/widget/mouse.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Constructs a mouse event structure.
+ *
+ * It receives a Gpm_Event event and translates it into a higher level protocol.
+ *
+ * Tip: for details on the C mouse API, see MC's lib/tty/mouse.h,
+ * or GPM's excellent 'info' manual:
+ *
+ * http://www.fifi.org/cgi-bin/info2www?(gpm)Event+Types
+ */
+static void
+init_mouse_event (mouse_event_t * event, mouse_msg_t msg, const Gpm_Event * global_gpm,
+ const Widget * w)
+{
+ event->msg = msg;
+ event->x = global_gpm->x - w->rect.x - 1; /* '-1' because Gpm_Event is 1-based. */
+ event->y = global_gpm->y - w->rect.y - 1;
+ event->count = global_gpm->type & (GPM_SINGLE | GPM_DOUBLE | GPM_TRIPLE);
+ event->buttons = global_gpm->buttons;
+ event->result.abort = FALSE;
+ event->result.repeat = FALSE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Translate GPM event to high-level event,
+ *
+ * @param w Widget object
+ * @param event GPM event
+ *
+ * @return high level mouse event
+ */
+static mouse_event_t
+mouse_translate_event (Widget * w, Gpm_Event * event)
+{
+ gboolean in_widget;
+ mouse_msg_t msg = MSG_MOUSE_NONE;
+ mouse_event_t local;
+
+ /*
+ * Very special widgets may want to control area outside their bounds.
+ * For such widgets you will have to turn on the 'forced_capture' flag.
+ * You'll also need, in your mouse handler, to inform the system of
+ * events you want to pass on by setting 'event->result.abort' to TRUE.
+ */
+ in_widget = w->mouse.forced_capture || mouse_global_in_widget (event, w);
+
+ if ((event->type & GPM_DOWN) != 0)
+ {
+ if (in_widget)
+ {
+ if ((event->buttons & GPM_B_UP) != 0)
+ msg = MSG_MOUSE_SCROLL_UP;
+ else if ((event->buttons & GPM_B_DOWN) != 0)
+ msg = MSG_MOUSE_SCROLL_DOWN;
+ else
+ {
+ /* Handle normal buttons: anything but the mouse wheel's.
+ *
+ * (Note that turning on capturing for the mouse wheel
+ * buttons doesn't make sense as they don't generate a
+ * mouse_up event, which means we'd never get uncaptured.)
+ */
+ w->mouse.capture = TRUE;
+ msg = MSG_MOUSE_DOWN;
+
+ w->mouse.last_buttons_down = event->buttons;
+ }
+ }
+ }
+ else if ((event->type & GPM_UP) != 0)
+ {
+ /* We trigger the mouse_up event even when !in_widget. That's
+ * because, for example, a paint application should stop drawing
+ * lines when the button is released even outside the canvas. */
+ if (w->mouse.capture)
+ {
+ w->mouse.capture = FALSE;
+ msg = MSG_MOUSE_UP;
+
+ /*
+ * When using xterm, event->buttons reports the buttons' state
+ * after the event occurred (meaning that event->buttons is zero,
+ * because the mouse button is now released). When using GPM,
+ * however, that field reports the button(s) that was released.
+ *
+ * The following makes xterm behave effectively like GPM:
+ */
+ if (event->buttons == 0)
+ event->buttons = w->mouse.last_buttons_down;
+ }
+ }
+ else if ((event->type & GPM_DRAG) != 0)
+ {
+ if (w->mouse.capture)
+ msg = MSG_MOUSE_DRAG;
+ }
+ else if ((event->type & GPM_MOVE) != 0)
+ {
+ if (in_widget)
+ msg = MSG_MOUSE_MOVE;
+ }
+
+ init_mouse_event (&local, msg, event, w);
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Call widget mouse handler to process high-level mouse event.
+ *
+ * Besides sending to the widget the event itself, this function may also
+ * send one or more pseudo events. Currently, MSG_MOUSE_CLICK is the only
+ * pseudo event in existence but in the future (e.g., with the introduction
+ * of a drag-drop API) there may be more.
+ *
+ * @param w Widget object
+ * @param event high level mouse event
+ *
+ * @return result of mouse event handling
+ */
+static int
+mouse_process_event (Widget * w, mouse_event_t * event)
+{
+ int ret = MOU_UNHANDLED;
+
+ if (event->msg != MSG_MOUSE_NONE)
+ {
+ w->mouse_callback (w, event->msg, event);
+
+ /* If a widget aborts a MSG_MOUSE_DOWN, we uncapture it so it
+ * doesn't steal events from other widgets. */
+ if (event->msg == MSG_MOUSE_DOWN && event->result.abort)
+ w->mouse.capture = FALSE;
+
+ /* Upon releasing the mouse button: if the mouse hasn't been dragged
+ * since the MSG_MOUSE_DOWN, we also trigger a click. */
+ if (event->msg == MSG_MOUSE_UP && w->mouse.last_msg == MSG_MOUSE_DOWN)
+ w->mouse_callback (w, MSG_MOUSE_CLICK, event);
+
+ /* Record the current event type for the benefit of the next event. */
+ w->mouse.last_msg = event->msg;
+
+ if (!event->result.abort)
+ ret = event->result.repeat ? MOU_REPEAT : MOU_NORMAL;
+ }
+
+ return ret;
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Translate GPM event to high-level event and process it
+ *
+ * @param w Widget object
+ * @param event GPM event
+ *
+ * @return result of mouse event handling
+ */
+int
+mouse_handle_event (Widget * w, Gpm_Event * event)
+{
+ mouse_event_t me;
+
+ me = mouse_translate_event (w, event);
+
+ return mouse_process_event (w, &me);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/mouse.h b/lib/widget/mouse.h
new file mode 100644
index 0000000..44e7b23
--- /dev/null
+++ b/lib/widget/mouse.h
@@ -0,0 +1,65 @@
+/** \file mouse.h
+ * \brief Header: Hight-level mouse API.
+ *
+ * This is a thin layer over the low-level mouse protocol in lib/tty/mouse.h.
+ * The latter is oblivious to the regions on the screen and is therefore a
+ * bit hard to use in widgets. This layer translates the low level Gpm_Event
+ * into something that's easy to work with in widgets.
+ */
+
+#ifndef MC__WIDGET_MOUSE_H
+#define MC__WIDGET_MOUSE_H
+
+#include "lib/tty/mouse.h" /* Gpm_Event */
+
+/*** enums ***************************************************************************************/
+
+/* Mouse messages */
+typedef enum
+{
+ /*
+ * Notes:
+ * (1) "anywhere" means "inside or outside the widget".
+ * (2) the mouse wheel is not considered "mouse button".
+ */
+ MSG_MOUSE_NONE = 0,
+ MSG_MOUSE_DOWN = 1, /* When mouse button is pressed down inside the widget. */
+ MSG_MOUSE_UP, /* When mouse button, previously pressed inside the widget, is released anywhere. */
+ MSG_MOUSE_CLICK, /* When mouse button, previously pressed inside the widget, is released inside the widget. */
+ MSG_MOUSE_DRAG, /* When a drag, initiated by button press inside the widget, occurs anywhere. */
+ MSG_MOUSE_MOVE, /* (Not currently implemented in MC.) */
+ MSG_MOUSE_SCROLL_UP, /* When mouse wheel is rotated away from the user. */
+ MSG_MOUSE_SCROLL_DOWN /* When mouse wheel is rotated towards the user. */
+} mouse_msg_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Mouse event structure. */
+typedef struct
+{
+ mouse_msg_t msg;
+
+ int x, y; /* Local to the widget. */
+ int buttons; /* Bitwise-or of: GPM_B_LEFT, GPM_B_MIDDLE, GPM_B_RIGHT */
+ int count; /* One of: GPM_SINGLE, GPM_DOUBLE, GPM_TRIPLE */
+
+ /* A mechanism for the callback to report back: */
+ struct
+ {
+ gboolean abort;
+ gboolean repeat;
+ } result;
+} mouse_event_t;
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* Translate GPM event to high-level event and process it */
+int mouse_handle_event (Widget * w, Gpm_Event * event);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_MOUSE_H */
diff --git a/lib/widget/quick.c b/lib/widget/quick.c
new file mode 100644
index 0000000..aec3070
--- /dev/null
+++ b/lib/widget/quick.c
@@ -0,0 +1,624 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 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 quick.c
+ * \brief Source: quick dialog engine
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h> /* fprintf() */
+
+#include "lib/global.h"
+#include "lib/strutil.h" /* str_term_width1() */
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#ifdef ENABLE_NLS
+#define I18N(x) (x = x != NULL && *x != '\0' ? _(x) : x)
+#else
+#define I18N(x) (x = x)
+#endif
+
+/*** file scope type declarations ****************************************************************/
+
+typedef struct
+{
+ Widget *widget;
+ quick_widget_t *quick_widget;
+} quick_widget_item_t;
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static WInput *
+quick_create_input (int y, int x, const quick_widget_t * qw)
+{
+ WInput *in;
+
+ in = input_new (y, x, input_colors, 8, qw->u.input.text, qw->u.input.histname,
+ qw->u.input.completion_flags);
+
+ in->is_password = qw->u.input.is_passwd;
+ in->strip_password = qw->u.input.strip_passwd;
+
+ return in;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+quick_create_labeled_input (GArray * widgets, int *y, int x, quick_widget_t * quick_widget,
+ int *width)
+{
+ quick_widget_item_t in, label;
+
+ label.quick_widget = g_new0 (quick_widget_t, 1);
+ label.quick_widget->widget_type = quick_label;
+ label.quick_widget->options = quick_widget->options;
+ label.quick_widget->state = quick_widget->state;
+ /* FIXME: this should be turned in depend of label_location */
+ label.quick_widget->pos_flags = quick_widget->pos_flags;
+
+ switch (quick_widget->u.input.label_location)
+ {
+ case input_label_above:
+ label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
+ *y += label.widget->rect.lines - 1;
+ g_array_append_val (widgets, label);
+
+ in.widget = WIDGET (quick_create_input (++(*y), x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
+ break;
+
+ case input_label_left:
+ label.widget = WIDGET (label_new (*y, x, I18N (quick_widget->u.input.label_text)));
+ g_array_append_val (widgets, label);
+
+ in.widget = WIDGET (quick_create_input (*y, x + label.widget->rect.cols + 1, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ *width = label.widget->rect.cols + in.widget->rect.cols + 1;
+ break;
+
+ case input_label_right:
+ in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ label.widget =
+ WIDGET (label_new
+ (*y, x + in.widget->rect.cols + 1, I18N (quick_widget->u.input.label_text)));
+ g_array_append_val (widgets, label);
+
+ *width = label.widget->rect.cols + in.widget->rect.cols + 1;
+ break;
+
+ case input_label_below:
+ in.widget = WIDGET (quick_create_input (*y, x, quick_widget));
+ in.quick_widget = quick_widget;
+ g_array_append_val (widgets, in);
+
+ label.widget = WIDGET (label_new (++(*y), x, I18N (quick_widget->u.input.label_text)));
+ *y += label.widget->rect.lines - 1;
+ g_array_append_val (widgets, label);
+
+ *width = MAX (label.widget->rect.cols, in.widget->rect.cols);
+ break;
+
+ default:
+ return;
+ }
+
+ INPUT (in.widget)->label = LABEL (label.widget);
+ /* cross references */
+ label.quick_widget->u.label.input = in.quick_widget;
+ in.quick_widget->u.input.label = label.quick_widget;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+int
+quick_dialog_skip (quick_dialog_t * quick_dlg, int nskip)
+{
+ int len;
+ int blen = 0;
+ int x, y; /* current positions */
+ int y1 = 0; /* bottom of 1st column in case of two columns */
+ int y2 = -1; /* start of two columns */
+ int width1 = 0; /* width of single column */
+ int width2 = 0; /* width of each of two columns */
+ gboolean have_groupbox = FALSE;
+ gboolean two_columns = FALSE;
+ gboolean put_buttons = FALSE;
+
+ /* x position of 1st column is 3 */
+ const int x1 = 3;
+ /* x position of 2nd column is 4 and it will be fixed later, after creation of all widgets */
+ int x2 = 4;
+
+ GArray *widgets;
+ size_t i;
+ quick_widget_t *quick_widget;
+ WGroupbox *g = NULL;
+ WDialog *dd;
+ GList *input_labels = NULL; /* Widgets not directly requested by the user. */
+ int return_val;
+
+ len = str_term_width1 (I18N (quick_dlg->title)) + 6;
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
+
+ y = 1;
+ x = x1;
+
+ /* create widgets */
+ widgets = g_array_sized_new (FALSE, FALSE, sizeof (quick_widget_item_t), 8);
+
+ for (quick_widget = quick_dlg->widgets; quick_widget->widget_type != quick_end; quick_widget++)
+ {
+ quick_widget_item_t item = { NULL, quick_widget };
+ int width = 0;
+
+ switch (quick_widget->widget_type)
+ {
+ case quick_checkbox:
+ item.widget =
+ WIDGET (check_new
+ (++y, x, *quick_widget->u.checkbox.state,
+ I18N (quick_widget->u.checkbox.text)));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_button:
+ /* single button */
+ item.widget = WIDGET (button_new (++y, x, quick_widget->u.button.action,
+ quick_widget->u.button.action == B_ENTER ?
+ DEFPUSH_BUTTON : NORMAL_BUTTON,
+ I18N (quick_widget->u.button.text),
+ quick_widget->u.button.callback));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_input:
+ *quick_widget->u.input.result = NULL;
+ y++;
+ if (quick_widget->u.input.label_location != input_label_none)
+ {
+ quick_create_labeled_input (widgets, &y, x, quick_widget, &width);
+ input_labels = g_list_prepend (input_labels, quick_widget->u.input.label);
+ }
+ else
+ {
+ item.widget = WIDGET (quick_create_input (y, x, quick_widget));
+ g_array_append_val (widgets, item);
+ width = item.widget->rect.cols;
+ }
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_label:
+ item.widget = WIDGET (label_new (++y, x, I18N (quick_widget->u.label.text)));
+ g_array_append_val (widgets, item);
+ y += item.widget->rect.lines - 1;
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ break;
+
+ case quick_radio:
+ {
+ WRadio *r;
+ char **items = NULL;
+
+ /* create the copy of radio_items to avoid mwmory leak */
+ items = g_new (char *, quick_widget->u.radio.count + 1);
+ for (i = 0; i < (size_t) quick_widget->u.radio.count; i++)
+ items[i] = g_strdup (_(quick_widget->u.radio.items[i]));
+ items[i] = NULL;
+
+ r = radio_new (++y, x, quick_widget->u.radio.count, (const char **) items);
+ r->pos = r->sel = *quick_widget->u.radio.value;
+ g_strfreev (items);
+ item.widget = WIDGET (r);
+ g_array_append_val (widgets, item);
+ y += item.widget->rect.lines - 1;
+ width = item.widget->rect.cols;
+ if (g != NULL)
+ width += 2;
+ if (two_columns)
+ width2 = MAX (width2, width);
+ else
+ width1 = MAX (width1, width);
+ }
+ break;
+
+ case quick_start_groupbox:
+ I18N (quick_widget->u.groupbox.title);
+ len = str_term_width1 (quick_widget->u.groupbox.title);
+ g = groupbox_new (++y, x, 1, len + 4, quick_widget->u.groupbox.title);
+ item.widget = WIDGET (g);
+ g_array_append_val (widgets, item);
+ have_groupbox = TRUE;
+ break;
+
+ case quick_stop_groupbox:
+ if (g != NULL)
+ {
+ Widget *w = WIDGET (g);
+
+ y++;
+ w->rect.lines = y + 1 - w->rect.y;
+ g = NULL;
+
+ g_array_append_val (widgets, item);
+ }
+ break;
+
+ case quick_separator:
+ y++;
+ if (quick_widget->u.separator.line)
+ {
+ item.widget = WIDGET (hline_new (y, x, 1));
+ g_array_append_val (widgets, item);
+ }
+ break;
+
+ case quick_start_columns:
+ y2 = y;
+ g_array_append_val (widgets, item);
+ two_columns = TRUE;
+ break;
+
+ case quick_next_column:
+ x = x2;
+ y1 = y;
+ y = y2;
+ break;
+
+ case quick_stop_columns:
+ x = x1;
+ y = MAX (y1, y);
+ g_array_append_val (widgets, item);
+ two_columns = FALSE;
+ break;
+
+ case quick_buttons:
+ /* start put several buttons in bottom line */
+ if (quick_widget->u.separator.space)
+ {
+ y++;
+
+ if (quick_widget->u.separator.line)
+ item.widget = WIDGET (hline_new (y, 1, -1));
+ }
+
+ g_array_append_val (widgets, item);
+
+ /* several buttons in bottom line */
+ y++;
+ quick_widget++;
+ for (; quick_widget->widget_type == quick_button; quick_widget++)
+ {
+ item.widget = WIDGET (button_new (y, x++, quick_widget->u.button.action,
+ quick_widget->u.button.action == B_ENTER ?
+ DEFPUSH_BUTTON : NORMAL_BUTTON,
+ I18N (quick_widget->u.button.text),
+ quick_widget->u.button.callback));
+ item.quick_widget = quick_widget;
+ g_array_append_val (widgets, item);
+ blen += item.widget->rect.cols + 1;
+ }
+
+ /* stop dialog build here */
+ blen--;
+ quick_widget->widget_type = quick_end;
+ quick_widget--;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /* adjust dialog width */
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, blen + 6);
+ if (have_groupbox)
+ {
+ if (width1 != 0)
+ width1 += 2;
+ if (width2 != 0)
+ width2 += 2;
+ }
+ if (width2 == 0)
+ len = width1 + 6;
+ else
+ {
+ len = width2 * 2 + 7;
+ if (width1 != 0)
+ len = MAX (len, width1 + 6);
+ }
+
+ quick_dlg->rect.cols = MAX (quick_dlg->rect.cols, len);
+ width1 = quick_dlg->rect.cols - 6;
+ width2 = (quick_dlg->rect.cols - 7) / 2;
+
+ if (quick_dlg->rect.x == -1 || quick_dlg->rect.y == -1)
+ dd = dlg_create (TRUE, 0, 0, y + 3, quick_dlg->rect.cols, WPOS_CENTER | WPOS_TRYUP, FALSE,
+ dialog_colors, quick_dlg->callback, quick_dlg->mouse_callback,
+ quick_dlg->help, quick_dlg->title);
+ else
+ dd = dlg_create (TRUE, quick_dlg->rect.y, quick_dlg->rect.x, y + 3, quick_dlg->rect.cols,
+ WPOS_KEEP_DEFAULT, FALSE, dialog_colors, quick_dlg->callback,
+ quick_dlg->mouse_callback, quick_dlg->help, quick_dlg->title);
+
+ /* add widgets into the dialog */
+ x2 = x1 + width2 + 1;
+ g = NULL;
+ two_columns = FALSE;
+ x = (WIDGET (dd)->rect.cols - blen) / 2;
+
+ for (i = 0; i < widgets->len; i++)
+ {
+ quick_widget_item_t *item;
+ int column_width;
+ WRect *r;
+
+ item = &g_array_index (widgets, quick_widget_item_t, i);
+ r = &item->widget->rect;
+ column_width = two_columns ? width2 : width1;
+
+ /* adjust widget width and x position */
+ switch (item->quick_widget->widget_type)
+ {
+ case quick_label:
+ {
+ quick_widget_t *input = item->quick_widget->u.label.input;
+
+ if (input != NULL && input->u.input.label_location == input_label_right)
+ {
+ /* location of this label will be adjusted later */
+ break;
+ }
+ }
+ MC_FALLTHROUGH;
+ case quick_checkbox:
+ case quick_radio:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ break;
+
+ case quick_button:
+ if (!put_buttons)
+ {
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ }
+ else
+ {
+ r->x = x;
+ x += r->cols + 1;
+ }
+ break;
+
+ case quick_input:
+ {
+ Widget *label = WIDGET (INPUT (item->widget)->label);
+ int width = column_width;
+
+ if (g != NULL)
+ width -= 4;
+
+ switch (item->quick_widget->u.input.label_location)
+ {
+ case input_label_left:
+ /* label was adjusted before; adjust input line */
+ r->x = label->rect.x + label->rect.cols + 1 - WIDGET (label->owner)->rect.x;
+ r->cols = width - label->rect.cols - 1;
+ break;
+
+ case input_label_right:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ r->cols = width - label->rect.cols - 1;
+ label->rect.x = r->x + r->cols + 1;
+ break;
+
+ default:
+ if (r->x != x1)
+ r->x = x2;
+ if (g != NULL)
+ r->x += 2;
+ r->cols = width;
+ break;
+ }
+
+ /* forced update internal variables of input line */
+ r->lines = 1;
+ widget_set_size_rect (item->widget, r);
+ }
+ break;
+
+ case quick_start_groupbox:
+ g = GROUPBOX (item->widget);
+ if (r->x != x1)
+ r->x = x2;
+ r->cols = column_width;
+ break;
+
+ case quick_stop_groupbox:
+ g = NULL;
+ break;
+
+ case quick_separator:
+ if (item->widget != NULL)
+ {
+ if (g != NULL)
+ {
+ Widget *wg = WIDGET (g);
+
+ HLINE (item->widget)->auto_adjust_cols = FALSE;
+ r->x = wg->rect.x + 1 - WIDGET (wg->owner)->rect.x;
+ r->cols = wg->rect.cols;
+ }
+ else if (two_columns)
+ {
+ HLINE (item->widget)->auto_adjust_cols = FALSE;
+ if (r->x != x1)
+ r->x = x2;
+ r->x--;
+ r->cols = column_width + 2;
+ }
+ else
+ HLINE (item->widget)->auto_adjust_cols = TRUE;
+ }
+ break;
+
+ case quick_start_columns:
+ two_columns = TRUE;
+ break;
+
+ case quick_stop_columns:
+ two_columns = FALSE;
+ break;
+
+ case quick_buttons:
+ /* several buttons in bottom line */
+ put_buttons = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (item->widget != NULL)
+ {
+ unsigned long id;
+
+ /* add widget into dialog */
+ item->widget->options |= item->quick_widget->options; /* FIXME: cannot reset flags, setup only */
+ item->widget->state |= item->quick_widget->state; /* FIXME: cannot reset flags, setup only */
+ id = group_add_widget_autopos (GROUP (dd), item->widget, item->quick_widget->pos_flags,
+ NULL);
+ if (item->quick_widget->id != NULL)
+ *item->quick_widget->id = id;
+ }
+ }
+
+ /* skip frame widget */
+ if (dd->bg != NULL)
+ nskip++;
+
+ while (nskip-- != 0)
+ group_set_current_widget_next (GROUP (dd));
+
+ return_val = dlg_run (dd);
+
+ /* Get the data if we found something interesting */
+ if (return_val != B_CANCEL)
+ for (i = 0; i < widgets->len; i++)
+ {
+ quick_widget_item_t *item;
+
+ item = &g_array_index (widgets, quick_widget_item_t, i);
+
+ switch (item->quick_widget->widget_type)
+ {
+ case quick_checkbox:
+ *item->quick_widget->u.checkbox.state = CHECK (item->widget)->state;
+ break;
+
+ case quick_input:
+ if ((item->quick_widget->u.input.completion_flags & INPUT_COMPLETE_CD) != 0)
+ *item->quick_widget->u.input.result =
+ tilde_expand (input_get_ctext (INPUT (item->widget)));
+ else
+ *item->quick_widget->u.input.result = input_get_text (INPUT (item->widget));
+ break;
+
+ case quick_radio:
+ *item->quick_widget->u.radio.value = RADIO (item->widget)->sel;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ widget_destroy (WIDGET (dd));
+
+ g_list_free_full (input_labels, g_free); /* destroy input labels created before */
+ g_array_free (widgets, TRUE);
+
+ return return_val;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/quick.h b/lib/widget/quick.h
new file mode 100644
index 0000000..e0dfece
--- /dev/null
+++ b/lib/widget/quick.h
@@ -0,0 +1,356 @@
+/** \file quick.h
+ * \brief Header: quick dialog engine
+ */
+
+#ifndef MC__QUICK_H
+#define MC__QUICK_H
+
+#include "lib/tty/mouse.h"
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define QUICK_CHECKBOX(txt, st, id_) \
+{ \
+ .widget_type = quick_checkbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .checkbox = { \
+ .text = txt, \
+ .state = st \
+ } \
+ } \
+}
+
+#define QUICK_BUTTON(txt, act, cb, id_) \
+{ \
+ .widget_type = quick_button, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .button = { \
+ .text = txt, \
+ .action = act, \
+ .callback = cb \
+ } \
+ } \
+}
+
+#define QUICK_INPUT(txt, hname, res, id_, is_passwd_, strip_passwd_, completion_flags_) \
+{ \
+ .widget_type = quick_input, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .input = { \
+ .label_text = NULL, \
+ .label_location = input_label_none, \
+ .label = NULL, \
+ .text = txt, \
+ .completion_flags = completion_flags_, \
+ .is_passwd = is_passwd_, \
+ .strip_passwd = strip_passwd_, \
+ .histname = hname, \
+ .result = res \
+ } \
+ } \
+}
+
+#define QUICK_LABELED_INPUT(label_, label_loc, txt, hname, res, id_, is_passwd_, strip_passwd_, completion_flags_) \
+{ \
+ .widget_type = quick_input, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .input = { \
+ .label_text = label_, \
+ .label_location = label_loc, \
+ .label = NULL, \
+ .text = txt, \
+ .completion_flags = completion_flags_, \
+ .is_passwd = is_passwd_, \
+ .strip_passwd = strip_passwd_, \
+ .histname = hname, \
+ .result = res \
+ } \
+ } \
+}
+
+#define QUICK_LABEL(txt, id_) \
+{ \
+ .widget_type = quick_label, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .label = { \
+ .text = txt, \
+ .input = NULL \
+ } \
+ } \
+}
+
+#define QUICK_RADIO(cnt, items_, val, id_) \
+{ \
+ .widget_type = quick_radio, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = id_, \
+ .u = { \
+ .radio = { \
+ .count = cnt, \
+ .items = items_, \
+ .value = val \
+ } \
+ } \
+}
+
+#define QUICK_START_GROUPBOX(t) \
+{ \
+ .widget_type = quick_start_groupbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .groupbox = { \
+ .title = t \
+ } \
+ } \
+}
+
+#define QUICK_STOP_GROUPBOX \
+{ \
+ .widget_type = quick_stop_groupbox, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_SEPARATOR(line_) \
+{ \
+ .widget_type = quick_separator, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .separator = { \
+ .space = TRUE, \
+ .line = line_ \
+ } \
+ } \
+}
+
+#define QUICK_START_COLUMNS \
+{ \
+ .widget_type = quick_start_columns, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_NEXT_COLUMN \
+{ \
+ .widget_type = quick_next_column, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_STOP_COLUMNS \
+{ \
+ .widget_type = quick_stop_columns, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+#define QUICK_START_BUTTONS(space_, line_) \
+{ \
+ .widget_type = quick_buttons, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .separator = { \
+ .space = space_, \
+ .line = line_ \
+ } \
+ } \
+}
+
+#define QUICK_BUTTONS_OK_CANCEL \
+ QUICK_START_BUTTONS (TRUE, TRUE), \
+ QUICK_BUTTON (N_("&OK"), B_ENTER, NULL, NULL), \
+ QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL)
+
+#define QUICK_END \
+{ \
+ .widget_type = quick_end, \
+ .options = WOP_DEFAULT, \
+ .pos_flags = WPOS_KEEP_DEFAULT, \
+ .id = NULL, \
+ .u = { \
+ .input = { \
+ .text = NULL, \
+ .histname = NULL, \
+ .result = NULL \
+ } \
+ } \
+}
+
+/*** enums ***************************************************************************************/
+
+/* Quick Widgets */
+typedef enum
+{
+ quick_end = 0,
+ quick_checkbox = 1,
+ quick_button = 2,
+ quick_input = 3,
+ quick_label = 4,
+ quick_radio = 5,
+ quick_start_groupbox = 6,
+ quick_stop_groupbox = 7,
+ quick_separator = 8,
+ quick_start_columns = 9,
+ quick_next_column = 10,
+ quick_stop_columns = 11,
+ quick_buttons = 12
+} quick_t;
+
+typedef enum
+{
+ input_label_none = 0,
+ input_label_above = 1,
+ input_label_left = 2,
+ input_label_right = 3,
+ input_label_below = 4
+} quick_input_label_location_t;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* The widget is placed on relative_?/divisions_? of the parent widget */
+typedef struct quick_widget_t quick_widget_t;
+
+struct quick_widget_t
+{
+ quick_t widget_type;
+
+ widget_options_t options;
+ widget_state_t state;
+ widget_pos_flags_t pos_flags;
+ unsigned long *id;
+
+ /* widget parameters */
+ union
+ {
+ struct
+ {
+ const char *text;
+ gboolean *state; /* in/out */
+ } checkbox;
+
+ struct
+ {
+ const char *text;
+ int action;
+ bcback_fn callback;
+ } button;
+
+ struct
+ {
+ const char *label_text;
+ quick_input_label_location_t label_location;
+ quick_widget_t *label;
+ const char *text;
+ input_complete_t completion_flags;
+ gboolean is_passwd; /* TRUE -- is password */
+ gboolean strip_passwd;
+ const char *histname;
+ char **result;
+ } input;
+
+ struct
+ {
+ const char *text;
+ quick_widget_t *input;
+ } label;
+
+ struct
+ {
+ int count;
+ const char **items;
+ int *value; /* in/out */
+ } radio;
+
+ struct
+ {
+ const char *title;
+ } groupbox;
+
+ struct
+ {
+ gboolean space;
+ gboolean line;
+ } separator;
+ } u;
+};
+
+typedef struct
+{
+ WRect rect; /* if rect.x == -1 or rect.y == -1, then dialog is ceneterd;
+ * rect.lines is unused and ignored */
+ const char *title;
+ const char *help;
+ quick_widget_t *widgets;
+ widget_cb_fn callback;
+ widget_mouse_cb_fn mouse_callback;
+} quick_dialog_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+int quick_dialog_skip (quick_dialog_t * quick_dlg, int nskip);
+
+/*** inline functions ****************************************************************************/
+
+static inline int
+quick_dialog (quick_dialog_t * quick_dlg)
+{
+ return quick_dialog_skip (quick_dlg, 1);
+}
+
+#endif /* MC__QUICK_H */
diff --git a/lib/widget/radio.c b/lib/widget/radio.c
new file mode 100644
index 0000000..5832f44
--- /dev/null
+++ b/lib/widget/radio.c
@@ -0,0 +1,249 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 radio.c
+ * \brief Source: WRadui widget (radiobuttons)
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+const global_keymap_t *radio_map = NULL;
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+radio_execute_cmd (WRadio * r, long command)
+{
+ cb_ret_t ret = MSG_HANDLED;
+ Widget *w = WIDGET (r);
+
+ switch (command)
+ {
+ case CK_Up:
+ case CK_Top:
+ if (r->pos == 0)
+ return MSG_NOT_HANDLED;
+
+ if (command == CK_Top)
+ r->pos = 0;
+ else
+ r->pos--;
+ widget_draw (w);
+ return MSG_HANDLED;
+
+ case CK_Down:
+ case CK_Bottom:
+ if (r->pos == r->count - 1)
+ return MSG_NOT_HANDLED;
+
+ if (command == CK_Bottom)
+ r->pos = r->count - 1;
+ else
+ r->pos++;
+ widget_draw (w);
+ return MSG_HANDLED;
+
+ case CK_Select:
+ r->sel = r->pos;
+ widget_set_state (w, WST_FOCUSED, TRUE); /* Also draws the widget */
+ send_message (w->owner, w, MSG_NOTIFY, 0, NULL);
+ return MSG_HANDLED;
+
+ default:
+ ret = MSG_NOT_HANDLED;
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Return MSG_HANDLED if we want a redraw */
+static cb_ret_t
+radio_key (WRadio * r, int key)
+{
+ long command;
+
+ command = widget_lookup_key (WIDGET (r), key);
+ if (command == CK_IgnoreKey)
+ return MSG_NOT_HANDLED;
+ return radio_execute_cmd (r, command);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+radio_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WRadio *r = RADIO (w);
+ int i;
+
+ switch (msg)
+ {
+ case MSG_HOTKEY:
+ for (i = 0; i < r->count; i++)
+ {
+ if (r->texts[i].hotkey != NULL)
+ {
+ int c;
+
+ c = g_ascii_tolower ((gchar) r->texts[i].hotkey[0]);
+ if (c != parm)
+ continue;
+ r->pos = i;
+
+ /* Take action */
+ send_message (w, sender, MSG_ACTION, CK_Select, data);
+ return MSG_HANDLED;
+ }
+ }
+ return MSG_NOT_HANDLED;
+
+ case MSG_KEY:
+ return radio_key (r, parm);
+
+ case MSG_ACTION:
+ return radio_execute_cmd (r, parm);
+
+ case MSG_CURSOR:
+ widget_gotoyx (r, r->pos, 1);
+ return MSG_HANDLED;
+
+ case MSG_DRAW:
+ {
+ gboolean focused;
+
+ focused = widget_get_state (w, WST_FOCUSED);
+
+ for (i = 0; i < r->count; i++)
+ {
+ widget_selectcolor (w, i == r->pos && focused, FALSE);
+ widget_gotoyx (w, i, 0);
+ tty_draw_hline (w->rect.y + i, w->rect.x, ' ', w->rect.cols);
+ tty_print_string ((r->sel == i) ? "(*) " : "( ) ");
+ hotkey_draw (w, r->texts[i], i == r->pos && focused);
+ }
+
+ return MSG_HANDLED;
+ }
+
+ case MSG_DESTROY:
+ for (i = 0; i < r->count; i++)
+ hotkey_free (r->texts[i]);
+ g_free (r->texts);
+ return MSG_HANDLED;
+
+ default:
+ return widget_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+radio_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ switch (msg)
+ {
+ case MSG_MOUSE_DOWN:
+ RADIO (w)->pos = event->y;
+ widget_select (w);
+ break;
+
+ case MSG_MOUSE_CLICK:
+ RADIO (w)->pos = event->y;
+ send_message (w, NULL, MSG_ACTION, CK_Select, NULL);
+ send_message (w->owner, w, MSG_POST_KEY, ' ', NULL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+WRadio *
+radio_new (int y, int x, int count, const char **texts)
+{
+ WRect r0 = { y, x, count, 1 };
+ WRadio *r;
+ Widget *w;
+ int i, wmax = 0;
+
+ r = g_new (WRadio, 1);
+ w = WIDGET (r);
+
+ /* Compute the longest string */
+ r->texts = g_new (hotkey_t, count);
+
+ for (i = 0; i < count; i++)
+ {
+ int width;
+
+ r->texts[i] = hotkey_new (texts[i]);
+ width = hotkey_width (r->texts[i]);
+ wmax = MAX (width, wmax);
+ }
+
+ /* 4 is width of "(*) " */
+ r0.cols = 4 + wmax;
+ widget_init (w, &r0, radio_callback, radio_mouse_callback);
+ w->options |= WOP_SELECTABLE | WOP_WANT_CURSOR | WOP_WANT_HOTKEY;
+ w->keymap = radio_map;
+
+ r->pos = 0;
+ r->sel = 0;
+ r->count = count;
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/radio.h b/lib/widget/radio.h
new file mode 100644
index 0000000..5b52382
--- /dev/null
+++ b/lib/widget/radio.h
@@ -0,0 +1,38 @@
+
+/** \file radio.h
+ * \brief Header: WRadio widget
+ */
+
+#ifndef MC__WIDGET_RADIO_H
+#define MC__WIDGET_RADIO_H
+
+#include "lib/keybind.h" /* global_keymap_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define RADIO(x) ((WRadio *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+typedef struct WRadio
+{
+ Widget widget;
+ int pos;
+ int sel;
+ int count; /* number of members */
+ hotkey_t *texts; /* texts of labels */
+} WRadio;
+
+/*** global variables defined in .c file *********************************************************/
+
+extern const global_keymap_t *radio_map;
+
+/*** declarations of public functions ************************************************************/
+
+WRadio *radio_new (int y, int x, int count, const char **text);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_RADIO_H */
diff --git a/lib/widget/rect.c b/lib/widget/rect.c
new file mode 100644
index 0000000..e2b4548
--- /dev/null
+++ b/lib/widget/rect.c
@@ -0,0 +1,253 @@
+/* Rectangular class for Midnight Commander widgets
+
+ Copyright (C) 2020-2022
+ The Free Software Foundation, Inc.
+
+ Written by:
+ Andrew Borodin <aborodin@vmail.ru>, 2020-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 widget-common.c
+ * \brief Source: shared stuff of widgets
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+
+#include "lib/global.h"
+
+#include "rect.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/*** file scope functions ************************************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create new WRect object.
+ *
+ * @param y y-coordinate of left-up corner
+ * @param x x-coordinate of left-up corner
+ * @param lines heigth
+ * @param cols width
+ *
+ * @return newly allocated WRect object.
+ */
+
+WRect *
+rect_new (int y, int x, int lines, int cols)
+{
+ WRect *r;
+
+ r = g_try_new (WRect, 1);
+
+ if (r != NULL)
+ rect_init (r, y, x, lines, cols);
+
+ return r;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize WRect object.
+ *
+ * @param r WRect object
+ * @param y y-coordinate of left-up corner
+ * @param x x-coordinate of left-up corner
+ * @param lines heigth
+ * @param cols width
+ */
+
+void
+rect_init (WRect * r, int y, int x, int lines, int cols)
+{
+ r->y = y;
+ r->x = x;
+ r->lines = lines;
+ r->cols = cols;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change position of rectangle area.
+ *
+ * @param r WRect object
+ * @param dy y-shift of left-up corner
+ * @param dx x-shift of left-up corner
+ */
+
+void
+rect_move (WRect * r, int dy, int dx)
+{
+ r->y += dy;
+ r->x += dx;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change size of rectangle area keeping it's position.
+ *
+ * @param r WRect object
+ * @param dl change size value of heigth
+ * @param dc change size value of width
+ */
+
+void
+rect_resize (WRect * r, int dl, int dc)
+{
+ r->lines += dl;
+ r->cols += dc;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change size of rectangle area keeping it's center.
+ *
+ * @param r WRect object
+ * @param dl change size value of y-coordinate and heigth
+ * Positive value means move up and increase heigth.
+ * Negative value means move down and decrease heigth.
+ * @param dc change size value of x-coordinate and width
+ * Positive value means move left and increase width.
+ * Negative value means move right and decrease width.
+ */
+
+void
+rect_grow (WRect * r, int dl, int dc)
+{
+ r->y -= dl;
+ r->x -= dc;
+ r->lines += dl * 2;
+ r->cols += dc * 2;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculates the intersection of two rectangle areas.
+ * The resulting rectangle is the largest rectangle which contains intersection of rectangle areas.
+ *
+ * @param r first WRect object
+ * @param r1 second WRect object
+ *
+ * The resulting rectangle is stored in r.
+ */
+
+void
+rect_intersect (WRect * r, const WRect * r1)
+{
+ int y, x;
+ int y1, x1;
+
+ /* right-down corners */
+ y = r->y + r->lines;
+ x = r->x + r->cols;
+ y1 = r1->y + r1->lines;
+ x1 = r1->x + r1->cols;
+
+ /* right-down corner of intersection */
+ y = MIN (y, y1);
+ x = MIN (x, x1);
+
+ /* left-up corner of intersection */
+ r->y = MAX (r->y, r1->y);
+ r->x = MAX (r->x, r1->x);
+
+ /* intersection sizes */
+ r->lines = y - r->y;
+ r->cols = x - r->x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculates the union of two rectangle areas.
+ * The resulting rectangle is the largest rectangle which contains both rectangle areas.
+ *
+ * @param r first WRect object
+ * @param r1 second WRect object
+ *
+ * The resulting rectangle is stored in r.
+ */
+
+void
+rect_union (WRect * r, const WRect * r1)
+{
+ int x, y;
+ int x1, y1;
+
+ /* right-down corners */
+ y = r->y + r->lines;
+ x = r->x + r->cols;
+ y1 = r1->y + r1->lines;
+ x1 = r1->x + r1->cols;
+
+ /* right-down corner of union */
+ y = MAX (y, y1);
+ x = MAX (x, x1);
+
+ /* left-up corner of union */
+ r->y = MIN (r->y, r1->y);
+ r->x = MIN (r->x, r1->x);
+
+ /* union sizes */
+ r->lines = y - r->y;
+ r->cols = x - r->x;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two rectangle areas are overlapped or not.
+ *
+ * @param r1 WRect object
+ * @param r2 WRect object
+ *
+ * @return TRUE if rectangle areas are overlapped, FALSE otherwise.
+ */
+
+gboolean
+rects_are_overlapped (const WRect * r1, const WRect * r2)
+{
+ return !((r2->x >= r1->x + r1->cols) || (r1->x >= r2->x + r2->cols)
+ || (r2->y >= r1->y + r1->lines) || (r1->y >= r2->y + r2->lines));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two rectangle areas are equal or not.
+ *
+ * @param r1 WRect object
+ * @param r2 WRect object
+ *
+ * @return TRUE if rectangle areas are equal, FALSE otherwise.
+ */
+
+gboolean
+rects_are_equal (const WRect * r1, const WRect * r2)
+{
+ return (r1->y == r2->y && r1->x == r2->x && r1->lines == r2->lines && r1->cols == r2->cols);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/rect.h b/lib/widget/rect.h
new file mode 100644
index 0000000..ca85968
--- /dev/null
+++ b/lib/widget/rect.h
@@ -0,0 +1,45 @@
+
+/** \file rect.h
+ * \brief Header: rectangular class
+ */
+
+#ifndef MC__WIDGET_RECT_H
+#define MC__WIDGET_RECT_H
+
+/*** typedefs (not structures) and defined constants *********************************************/
+
+#define RECT(x) ((WRect *)(x))
+#define CONST_RECT(x) ((const WRect *)(x))
+
+/*** enums ***************************************************************************************/
+
+/*** structures declarations (and typedefs of structures) ****************************************/
+
+struct WRect;
+typedef struct WRect WRect;
+
+struct WRect
+{
+ int y;
+ int x;
+ int lines;
+ int cols;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+WRect *rect_new (int y, int x, int lines, int cols);
+void rect_init (WRect * r, int y, int x, int lines, int cols);
+void rect_move (WRect * r, int dy, int dx);
+void rect_resize (WRect * r, int dl, int dc);
+void rect_grow (WRect * r, int dl, int dc);
+void rect_intersect (WRect * r, const WRect * r1);
+void rect_union (WRect * r, const WRect * r1);
+gboolean rects_are_overlapped (const WRect * r1, const WRect * r2);
+gboolean rects_are_equal (const WRect * r1, const WRect * r2);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WIDGET_RECT_H */
diff --git a/lib/widget/widget-common.c b/lib/widget/widget-common.c
new file mode 100644
index 0000000..bc36e6d
--- /dev/null
+++ b/lib/widget/widget-common.c
@@ -0,0 +1,898 @@
+/*
+ Widgets for the Midnight Commander
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Radek Doulik, 1994, 1995
+ Miguel de Icaza, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 1996
+ Norbert Warmuth, 1997
+ 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 widget-common.c
+ * \brief Source: shared stuff of widgets
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h"
+#include "lib/tty/color.h"
+#include "lib/skin.h"
+#include "lib/strutil.h"
+#include "lib/widget.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+/* maximum value of used widget ID */
+static unsigned long widget_id = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Calc widget ID,
+ * Widget ID is uniq for each widget created during MC session (like PID in OS).
+ *
+ * @return widget ID.
+ */
+static unsigned long
+widget_set_id (void)
+{
+ unsigned long id;
+
+ id = widget_id++;
+ /* TODO IF NEEDED: if id is already used, find next free id. */
+
+ return id;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static cb_ret_t
+widget_default_resize (Widget * w, const WRect * r)
+{
+ if (r == NULL)
+ return MSG_NOT_HANDLED;
+
+ w->rect = *r;
+
+ return MSG_HANDLED;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+widget_do_focus (Widget * w, gboolean enable)
+{
+ if (w != NULL && widget_get_state (WIDGET (w->owner), WST_VISIBLE | WST_FOCUSED))
+ widget_set_state (w, WST_FOCUSED, enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Focus specified widget in it's owner.
+ *
+ * @param w widget to be focused.
+ */
+
+static void
+widget_focus (Widget * w)
+{
+ WGroup *g = w->owner;
+
+ if (g == NULL)
+ return;
+
+ if (WIDGET (g->current->data) != w)
+ {
+ widget_do_focus (WIDGET (g->current->data), FALSE);
+ /* Test if focus lost was allowed and focus has really been loose */
+ if (g->current == NULL || !widget_get_state (WIDGET (g->current->data), WST_FOCUSED))
+ {
+ widget_do_focus (w, TRUE);
+ g->current = widget_find (WIDGET (g), w);
+ }
+ }
+ else if (!widget_get_state (w, WST_FOCUSED))
+ widget_do_focus (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Put widget on top or bottom of Z-order.
+ */
+static void
+widget_reorder (GList * l, gboolean set_top)
+{
+ WGroup *g = WIDGET (l->data)->owner;
+
+ g->widgets = g_list_remove_link (g->widgets, l);
+ if (set_top)
+ g->widgets = g_list_concat (g->widgets, l);
+ else
+ g->widgets = g_list_concat (l, g->widgets);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+hotkey_cmp (const char *s1, const char *s2)
+{
+ gboolean n1, n2;
+
+ n1 = s1 != NULL;
+ n2 = s2 != NULL;
+
+ if (n1 != n2)
+ return FALSE;
+
+ if (n1 && n2 && strcmp (s1, s2) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+widget_default_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
+{
+ /* do nothing */
+ (void) w;
+ (void) msg;
+ (void) event;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static const int *
+widget_default_get_colors (const Widget * w)
+{
+ const Widget *owner = CONST_WIDGET (w->owner);
+
+ return (owner == NULL ? NULL : widget_get_colors (owner));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct hotkey_t
+hotkey_new (const char *text)
+{
+ hotkey_t result;
+ const char *cp, *p;
+
+ if (text == NULL)
+ text = "";
+
+ /* search for '&', that is not on the of text */
+ cp = strchr (text, '&');
+ if (cp != NULL && cp[1] != '\0')
+ {
+ result.start = g_strndup (text, cp - text);
+
+ /* skip '&' */
+ cp++;
+ p = str_cget_next_char (cp);
+ result.hotkey = g_strndup (cp, p - cp);
+
+ cp = p;
+ result.end = g_strdup (cp);
+ }
+ else
+ {
+ result.start = g_strdup (text);
+ result.hotkey = NULL;
+ result.end = NULL;
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotkey_free (const hotkey_t hotkey)
+{
+ g_free (hotkey.start);
+ g_free (hotkey.hotkey);
+ g_free (hotkey.end);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+hotkey_width (const hotkey_t hotkey)
+{
+ int result;
+
+ result = str_term_width1 (hotkey.start);
+ result += (hotkey.hotkey != NULL) ? str_term_width1 (hotkey.hotkey) : 0;
+ result += (hotkey.end != NULL) ? str_term_width1 (hotkey.end) : 0;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+hotkey_equal (const hotkey_t hotkey1, const hotkey_t hotkey2)
+{
+ /* *INDENT-OFF* */
+ return (strcmp (hotkey1.start, hotkey2.start) == 0) &&
+ hotkey_cmp (hotkey1.hotkey, hotkey2.hotkey) &&
+ hotkey_cmp (hotkey1.end, hotkey2.end);
+ /* *INDENT-ON* */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+hotkey_draw (Widget * w, const hotkey_t hotkey, gboolean focused)
+{
+ if (hotkey.start[0] != '\0')
+ {
+ widget_selectcolor (w, focused, FALSE);
+ tty_print_string (hotkey.start);
+ }
+
+ if (hotkey.hotkey != NULL)
+ {
+ widget_selectcolor (w, focused, TRUE);
+ tty_print_string (hotkey.hotkey);
+ }
+
+ if (hotkey.end != NULL)
+ {
+ widget_selectcolor (w, focused, FALSE);
+ tty_print_string (hotkey.end);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+hotkey_get_text (const hotkey_t hotkey)
+{
+ GString *text;
+
+ text = g_string_new (hotkey.start);
+
+ if (hotkey.hotkey != NULL)
+ {
+ g_string_append_c (text, '&');
+ g_string_append (text, hotkey.hotkey);
+ }
+
+ if (hotkey.end != NULL)
+ g_string_append (text, hotkey.end);
+
+ return g_string_free (text, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_init (Widget * w, const WRect * r, widget_cb_fn callback, widget_mouse_cb_fn mouse_callback)
+{
+ w->id = widget_set_id ();
+ w->rect = *r;
+ w->pos_flags = WPOS_KEEP_DEFAULT;
+ w->callback = callback;
+
+ w->keymap = NULL;
+ w->ext_keymap = NULL;
+ w->ext_mode = FALSE;
+
+ w->mouse_callback = mouse_callback != NULL ? mouse_callback : widget_default_mouse_callback;
+ w->owner = NULL;
+ w->mouse_handler = mouse_handle_event;
+ w->mouse.forced_capture = FALSE;
+ w->mouse.capture = FALSE;
+ w->mouse.last_msg = MSG_MOUSE_NONE;
+ w->mouse.last_buttons_down = 0;
+
+ w->options = WOP_DEFAULT;
+ w->state = WST_CONSTRUCT | WST_VISIBLE;
+
+ w->make_global = widget_default_make_global;
+ w->make_local = widget_default_make_local;
+
+ w->make_global = widget_default_make_global;
+ w->make_local = widget_default_make_local;
+
+ w->find = widget_default_find;
+ w->find_by_type = widget_default_find_by_type;
+ w->find_by_id = widget_default_find_by_id;
+
+ w->set_state = widget_default_set_state;
+ w->destroy = widget_default_destroy;
+ w->get_colors = widget_default_get_colors;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Default callback for widgets */
+cb_ret_t
+widget_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ (void) sender;
+ (void) parm;
+
+ switch (msg)
+ {
+ case MSG_INIT:
+ case MSG_FOCUS:
+ case MSG_UNFOCUS:
+ case MSG_ENABLE:
+ case MSG_DISABLE:
+ case MSG_DRAW:
+ case MSG_DESTROY:
+ case MSG_CURSOR:
+ case MSG_IDLE:
+ return MSG_HANDLED;
+
+ case MSG_RESIZE:
+ return widget_default_resize (w, CONST_RECT (data));
+
+ default:
+ return MSG_NOT_HANDLED;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Apply new options to widget.
+ *
+ * @param w widget
+ * @param options widget option flags to modify. Several flags per call can be modified.
+ * @param enable TRUE if specified options should be added, FALSE if options should be removed
+ */
+void
+widget_set_options (Widget * w, widget_options_t options, gboolean enable)
+{
+ if (enable)
+ w->options |= options;
+ else
+ w->options &= ~options;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_adjust_position (widget_pos_flags_t pos_flags, WRect * r)
+{
+ if ((pos_flags & WPOS_FULLSCREEN) != 0)
+ {
+ r->y = 0;
+ r->x = 0;
+ r->lines = LINES;
+ r->cols = COLS;
+ }
+ else
+ {
+ if ((pos_flags & WPOS_CENTER_HORZ) != 0)
+ r->x = (COLS - r->cols) / 2;
+
+ if ((pos_flags & WPOS_CENTER_VERT) != 0)
+ r->y = (LINES - r->lines) / 2;
+
+ if ((pos_flags & WPOS_TRYUP) != 0)
+ {
+ if (r->y > 3)
+ r->y -= 2;
+ else if (r->y == 3)
+ r->y = 2;
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change widget position and size.
+ *
+ * @param w widget
+ * @param y y coordinate of top-left corner
+ * @param x x coordinate of top-left corner
+ * @param lines width
+ * @param cols height
+ */
+
+void
+widget_set_size (Widget * w, int y, int x, int lines, int cols)
+{
+ WRect r = { y, x, lines, cols };
+
+ send_message (w, NULL, MSG_RESIZE, 0, &r);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Change widget position and size.
+ *
+ * @param w widget
+ * @param r WRect obgect that holds position and size
+ */
+
+void
+widget_set_size_rect (Widget * w, WRect * r)
+{
+ send_message (w, NULL, MSG_RESIZE, 0, r);
+ widget_draw (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_selectcolor (Widget * w, gboolean focused, gboolean hotkey)
+{
+ int color;
+ const int *colors;
+
+ colors = widget_get_colors (w);
+
+ if (widget_get_state (w, WST_DISABLED))
+ color = DISABLED_COLOR;
+ else if (hotkey)
+ color = colors[focused ? DLG_COLOR_HOT_FOCUS : DLG_COLOR_HOT_NORMAL];
+ else
+ color = colors[focused ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL];
+
+ tty_setcolor (color);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_erase (Widget * w)
+{
+ if (w != NULL)
+ tty_fill_region (w->rect.y, w->rect.x, w->rect.lines, w->rect.cols, ' ');
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+widget_set_visibility (Widget * w, gboolean make_visible)
+{
+ if (widget_get_state (w, WST_VISIBLE) != make_visible)
+ widget_set_state (w, WST_VISIBLE, make_visible);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether widget is active or not.
+ * Widget is active if it's current in the its owner and each owner in the chain is current too.
+ *
+ * @param w the widget
+ *
+ * @return TRUE if the widget is active, FALSE otherwise
+ */
+
+gboolean
+widget_is_active (const void *w)
+{
+ const WGroup *owner;
+
+ /* Is group top? */
+ if (w == top_dlg->data)
+ return TRUE;
+
+ owner = CONST_WIDGET (w)->owner;
+
+ /* Is widget in any group? */
+ if (owner == NULL)
+ return FALSE;
+
+ if (w != owner->current->data)
+ return FALSE;
+
+ return widget_is_active (owner);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+cb_ret_t
+widget_draw (Widget * w)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+ if (w != NULL && widget_get_state (w, WST_VISIBLE))
+ {
+ WGroup *g = w->owner;
+
+ if (g != NULL && widget_get_state (WIDGET (g), WST_ACTIVE))
+ ret = w->callback (w, NULL, MSG_DRAW, 0, NULL);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Replace widget in the dialog.
+ *
+ * @param old_w old widget that need to be replaced
+ * @param new_w new widget that will replace @old_w
+ */
+
+void
+widget_replace (Widget * old_w, Widget * new_w)
+{
+ WGroup *g = old_w->owner;
+ gboolean should_focus = FALSE;
+ GList *holder;
+
+ if (g->widgets == NULL)
+ return;
+
+ if (g->current == NULL)
+ g->current = g->widgets;
+
+ /* locate widget position in the list */
+ if (old_w == g->current->data)
+ holder = g->current;
+ else
+ holder = g_list_find (g->widgets, old_w);
+
+ /* if old widget is focused, we should focus the new one... */
+ if (widget_get_state (old_w, WST_FOCUSED))
+ should_focus = TRUE;
+ /* ...but if new widget isn't selectable, we cannot focus it */
+ if (!widget_get_options (new_w, WOP_SELECTABLE))
+ should_focus = FALSE;
+
+ /* if new widget isn't selectable, select other widget before replace */
+ if (!should_focus)
+ {
+ GList *l;
+
+ for (l = group_get_widget_next_of (holder);
+ !widget_is_focusable (WIDGET (l->data)) && l != holder;
+ l = group_get_widget_next_of (l))
+ ;
+
+ widget_select (WIDGET (l->data));
+ }
+
+ /* replace widget */
+ new_w->owner = g;
+ new_w->id = old_w->id;
+ holder->data = new_w;
+
+ send_message (old_w, NULL, MSG_DESTROY, 0, NULL);
+ send_message (new_w, NULL, MSG_INIT, 0, NULL);
+
+ if (should_focus)
+ widget_select (new_w);
+ else
+ widget_draw (new_w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+widget_is_focusable (const Widget * w)
+{
+ return (widget_get_options (w, WOP_SELECTABLE) && widget_get_state (w, WST_VISIBLE) &&
+ !widget_get_state (w, WST_DISABLED));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Select specified widget in it's owner.
+ *
+ * Note: this function (and widget_focus(), which it calls) is a no-op
+ * if the widget is already selected.
+ *
+ * @param w widget to be selected
+ */
+
+void
+widget_select (Widget * w)
+{
+ WGroup *g;
+
+ if (!widget_get_options (w, WOP_SELECTABLE))
+ return;
+
+ g = GROUP (w->owner);
+ if (g != NULL)
+ {
+ if (widget_get_options (w, WOP_TOP_SELECT))
+ {
+ GList *l;
+
+ l = widget_find (WIDGET (g), w);
+ widget_reorder (l, TRUE);
+ }
+
+ widget_focus (w);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Set widget at bottom of widget list.
+ */
+
+void
+widget_set_bottom (Widget * w)
+{
+ widget_reorder (widget_find (WIDGET (w->owner), w), FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Look up key event of widget and translate it to command ID.
+ * @param w widget
+ * @param key key event
+ *
+ * @return command ID binded with @key.
+ */
+
+long
+widget_lookup_key (Widget * w, int key)
+{
+ if (w->ext_mode)
+ {
+ w->ext_mode = FALSE;
+ return keybind_lookup_keymap_command (w->ext_keymap, key);
+ }
+
+ return keybind_lookup_keymap_command (w->keymap, key);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default widget callback to convert widget coordinates from local (relative to owner) to global
+ * (relative to screen).
+ *
+ * @param w widget
+ * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
+ */
+
+void
+widget_default_make_global (Widget * w, const WRect * delta)
+{
+ if (delta != NULL)
+ rect_move (&w->rect, delta->y, delta->x);
+ else if (w->owner != NULL)
+ rect_move (&w->rect, WIDGET (w->owner)->rect.y, WIDGET (w->owner)->rect.x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default widget callback to convert widget coordinates from global (relative to screen) to local
+ * (relative to owner).
+ *
+ * @param w widget
+ * @delta offset for top-left corner coordinates. Used for child widgets of WGroup
+ */
+
+void
+widget_default_make_local (Widget * w, const WRect * delta)
+{
+ if (delta != NULL)
+ rect_move (&w->rect, -delta->y, -delta->x);
+ else if (w->owner != NULL)
+ rect_move (&w->rect, -WIDGET (w->owner)->rect.y, -WIDGET (w->owner)->rect.x);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to find widget.
+ *
+ * @param w widget
+ * @param what widget to find
+ *
+ * @return holder of @what if widget is @what, NULL otherwise
+ */
+
+GList *
+widget_default_find (const Widget * w, const Widget * what)
+{
+ return (w != what
+ || w->owner == NULL) ? NULL : g_list_find (CONST_GROUP (w->owner)->widgets, what);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default callback function to find widget by widget type using widget callback.
+ *
+ * @param w widget
+ * @param cb widget callback
+ *
+ * @return @w if widget callback is @cb, NULL otherwise
+ */
+
+Widget *
+widget_default_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ return (w->callback == cb ? WIDGET (w) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to find widget by widget ID.
+ *
+ * @param w widget
+ * @param id widget ID
+ *
+ * @return @w if widget id is equal to @id, NULL otherwise
+ */
+
+Widget *
+widget_default_find_by_id (const Widget * w, unsigned long id)
+{
+ return (w->id == id ? WIDGET (w) : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Default callback function to modify state of widget.
+ *
+ * @param w widget
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+
+cb_ret_t
+widget_default_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ gboolean ret = MSG_HANDLED;
+ Widget *owner = WIDGET (GROUP (w->owner));
+
+ if (enable)
+ w->state |= state;
+ else
+ w->state &= ~state;
+
+ if (enable)
+ {
+ /* exclusive bits */
+ if ((state & WST_CONSTRUCT) != 0)
+ w->state &= ~(WST_ACTIVE | WST_SUSPENDED | WST_CLOSED);
+ else if ((state & WST_ACTIVE) != 0)
+ w->state &= ~(WST_CONSTRUCT | WST_SUSPENDED | WST_CLOSED);
+ else if ((state & WST_SUSPENDED) != 0)
+ w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_CLOSED);
+ else if ((state & WST_CLOSED) != 0)
+ w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_SUSPENDED);
+ }
+
+ if (owner == NULL)
+ return MSG_NOT_HANDLED;
+
+ switch (state)
+ {
+ case WST_VISIBLE:
+ if (widget_get_state (owner, WST_ACTIVE))
+ {
+ /* redraw owner to show/hide widget */
+ widget_draw (owner);
+
+ if (!enable)
+ {
+ /* try select another widget if current one got hidden */
+ if (w == GROUP (owner)->current->data)
+ group_select_next_widget (GROUP (owner));
+
+ widget_update_cursor (owner); /* FIXME: unneeded? */
+ }
+ }
+ break;
+
+
+ case WST_DISABLED:
+ ret = send_message (w, NULL, enable ? MSG_DISABLE : MSG_ENABLE, 0, NULL);
+ if (ret == MSG_HANDLED && widget_get_state (owner, WST_ACTIVE))
+ ret = widget_draw (w);
+ break;
+
+ case WST_FOCUSED:
+ {
+ widget_msg_t msg;
+
+ msg = enable ? MSG_FOCUS : MSG_UNFOCUS;
+ ret = send_message (w, NULL, msg, 0, NULL);
+ if (ret == MSG_HANDLED && widget_get_state (owner, WST_ACTIVE))
+ {
+ widget_draw (w);
+ /* Notify owner that focus was moved from one widget to another */
+ send_message (owner, w, MSG_CHANGED_FOCUS, 0, NULL);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Default callback function to destroy widget.
+ *
+ * @param w widget
+ */
+
+void
+widget_default_destroy (Widget * w)
+{
+ send_message (w, NULL, MSG_DESTROY, 0, NULL);
+ g_free (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* get mouse pointer location within widget */
+
+Gpm_Event
+mouse_get_local (const Gpm_Event * global, const Widget * w)
+{
+ Gpm_Event local;
+
+ memset (&local, 0, sizeof (local));
+
+ local.buttons = global->buttons;
+ local.x = global->x - w->rect.x;
+ local.y = global->y - w->rect.y;
+ local.type = global->type;
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+gboolean
+mouse_global_in_widget (const Gpm_Event * event, const Widget * w)
+{
+ const WRect *r = &w->rect;
+
+ return (event->x > r->x) && (event->y > r->y) && (event->x <= r->x + r->cols)
+ && (event->y <= r->y + r->lines);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/widget-common.h b/lib/widget/widget-common.h
new file mode 100644
index 0000000..f5a53de
--- /dev/null
+++ b/lib/widget/widget-common.h
@@ -0,0 +1,462 @@
+
+/** \file widget-common.h
+ * \brief Header: shared stuff of widgets
+ */
+
+#ifndef MC__WIDGET_INTERNAL_H
+#define MC__WIDGET_INTERNAL_H
+
+#include "lib/keybind.h" /* global_keymap_t */
+#include "lib/tty/mouse.h"
+#include "lib/widget/mouse.h" /* mouse_msg_t, mouse_event_t */
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+#define WIDGET(x) ((Widget *)(x))
+#define CONST_WIDGET(x) ((const Widget *)(x))
+
+#define widget_gotoyx(w, _y, _x) tty_gotoyx (CONST_WIDGET(w)->rect.y + (_y), CONST_WIDGET(w)->rect.x + (_x))
+/* Sets/clear the specified flag in the options field */
+#define widget_want_cursor(w,i) widget_set_options(w, WOP_WANT_CURSOR, i)
+#define widget_want_hotkey(w,i) widget_set_options(w, WOP_WANT_HOTKEY, i)
+#define widget_want_tab(w,i) widget_set_options(w, WOP_WANT_TAB, i)
+#define widget_idle(w,i) widget_set_state(w, WST_IDLE, i)
+#define widget_disable(w,i) widget_set_state(w, WST_DISABLED, i)
+
+/*** enums ***************************************************************************************/
+
+/* Widget messages */
+typedef enum
+{
+ MSG_INIT = 0, /* Initialize widget */
+ MSG_FOCUS, /* Draw widget in focused state or widget has got focus */
+ MSG_UNFOCUS, /* Draw widget in unfocused state or widget has been unfocused */
+ MSG_CHANGED_FOCUS, /* Notification to owner about focus state change */
+ MSG_ENABLE, /* Change state to enabled */
+ MSG_DISABLE, /* Change state to disabled */
+ MSG_DRAW, /* Draw widget on screen */
+ MSG_KEY, /* Sent to widgets on key press */
+ MSG_HOTKEY, /* Sent to widget to catch preprocess key */
+ MSG_HOTKEY_HANDLED, /* A widget has got the hotkey */
+ MSG_UNHANDLED_KEY, /* Key that no widget handled */
+ MSG_POST_KEY, /* The key has been handled */
+ MSG_ACTION, /* Send to widget to handle command */
+ MSG_NOTIFY, /* Typically sent to dialog to inform it of state-change
+ * of listboxes, check- and radiobuttons. */
+ MSG_CURSOR, /* Sent to widget to position the cursor */
+ MSG_IDLE, /* The idle state is active */
+ MSG_RESIZE, /* Screen size has changed */
+ MSG_VALIDATE, /* Dialog is to be closed */
+ MSG_END, /* Shut down dialog */
+ MSG_DESTROY /* Sent to widget at destruction time */
+} widget_msg_t;
+
+/* Widgets are expected to answer to the following messages:
+ MSG_FOCUS: MSG_HANDLED if the accept the focus, MSG_NOT_HANDLED if they do not.
+ MSG_UNFOCUS: MSG_HANDLED if they accept to release the focus, MSG_NOT_HANDLED if they don't.
+ MSG_KEY: MSG_HANDLED if they actually used the key, MSG_NOT_HANDLED if not.
+ MSG_HOTKEY: MSG_HANDLED if they actually used the key, MSG_NOT_HANDLED if not.
+ */
+
+typedef enum
+{
+ MSG_NOT_HANDLED = 0,
+ MSG_HANDLED = 1
+} cb_ret_t;
+
+/* Widget options */
+typedef enum
+{
+ WOP_DEFAULT = (0 << 0),
+ WOP_WANT_HOTKEY = (1 << 0),
+ WOP_WANT_CURSOR = (1 << 1),
+ WOP_WANT_TAB = (1 << 2), /* Should the tab key be sent to the dialog? */
+ WOP_IS_INPUT = (1 << 3),
+ WOP_SELECTABLE = (1 << 4),
+ WOP_TOP_SELECT = (1 << 5)
+} widget_options_t;
+
+/* Widget state */
+typedef enum
+{
+ WST_DEFAULT = (0 << 0),
+ WST_VISIBLE = (1 << 0), /* Widget is visible */
+ WST_DISABLED = (1 << 1), /* Widget cannot be selected */
+ WST_IDLE = (1 << 2),
+ WST_MODAL = (1 << 3), /* Widget (dialog) is modal */
+ WST_FOCUSED = (1 << 4),
+
+ WST_CONSTRUCT = (1 << 15), /* Widget has been constructed but not run yet */
+ WST_ACTIVE = (1 << 16), /* Dialog is visible and active */
+ WST_SUSPENDED = (1 << 17), /* Dialog is suspended */
+ WST_CLOSED = (1 << 18) /* Dialog is closed */
+} widget_state_t;
+
+/* Flags for widget repositioning on dialog resize */
+typedef enum
+{
+ WPOS_FULLSCREEN = (1 << 0), /* widget occupies the whole screen */
+ WPOS_CENTER_HORZ = (1 << 1), /* center widget in horizontal */
+ WPOS_CENTER_VERT = (1 << 2), /* center widget in vertical */
+ WPOS_CENTER = WPOS_CENTER_HORZ | WPOS_CENTER_VERT, /* center widget */
+ WPOS_TRYUP = (1 << 3), /* try to move two lines up the widget */
+ WPOS_KEEP_LEFT = (1 << 4), /* keep widget distance to left border of dialog */
+ WPOS_KEEP_RIGHT = (1 << 5), /* keep widget distance to right border of dialog */
+ WPOS_KEEP_TOP = (1 << 6), /* keep widget distance to top border of dialog */
+ WPOS_KEEP_BOTTOM = (1 << 7), /* keep widget distance to bottom border of dialog */
+ WPOS_KEEP_HORZ = WPOS_KEEP_LEFT | WPOS_KEEP_RIGHT,
+ WPOS_KEEP_VERT = WPOS_KEEP_TOP | WPOS_KEEP_BOTTOM,
+ WPOS_KEEP_ALL = WPOS_KEEP_HORZ | WPOS_KEEP_VERT,
+ WPOS_KEEP_DEFAULT = WPOS_KEEP_LEFT | WPOS_KEEP_TOP
+} widget_pos_flags_t;
+/* NOTES:
+ * If WPOS_FULLSCREEN is set then all other position flags are ignored.
+ * If WPOS_CENTER_HORZ flag is used, other horizontal flags (WPOS_KEEP_LEFT, WPOS_KEEP_RIGHT,
+ * and WPOS_KEEP_HORZ) are ignored.
+ * If WPOS_CENTER_VERT flag is used, other horizontal flags (WPOS_KEEP_TOP, WPOS_KEEP_BOTTOM,
+ * and WPOS_KEEP_VERT) are ignored.
+ */
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Widget callback */
+typedef cb_ret_t (*widget_cb_fn) (Widget * widget, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+/* Widget mouse callback */
+typedef void (*widget_mouse_cb_fn) (Widget * w, mouse_msg_t msg, mouse_event_t * event);
+/* translate mouse event and process it */
+typedef int (*widget_mouse_handle_fn) (Widget * w, Gpm_Event * event);
+
+/* Every Widget must have this as its first element */
+struct Widget
+{
+ WRect rect; /* position and size */
+ /* ATTENTION! For groups, don't change @rect members directly to avoid
+ incorrect reposion and resize of group members. */
+ widget_pos_flags_t pos_flags; /* repositioning flags */
+ widget_options_t options;
+ widget_state_t state;
+ unsigned long id; /* uniq widget ID */
+ widget_cb_fn callback;
+ widget_mouse_cb_fn mouse_callback;
+ WGroup *owner;
+
+ /* Key-related fields */
+ const global_keymap_t *keymap; /* main keymap */
+ const global_keymap_t *ext_keymap; /* extended keymap */
+ gboolean ext_mode; /* use keymap or ext_keymap */
+
+ /* Mouse-related fields. */
+ widget_mouse_handle_fn mouse_handler;
+ struct
+ {
+ /* Public members: */
+ gboolean forced_capture; /* Overrides the 'capture' member. Set explicitly by the programmer. */
+
+ /* Implementation details: */
+ gboolean capture; /* Whether the widget "owns" the mouse. */
+ mouse_msg_t last_msg; /* The previous event type processed. */
+ int last_buttons_down;
+ } mouse;
+
+ void (*make_global) (Widget * w, const WRect * delta);
+ void (*make_local) (Widget * w, const WRect * delta);
+
+ GList *(*find) (const Widget * w, const Widget * what);
+ Widget *(*find_by_type) (const Widget * w, widget_cb_fn cb);
+ Widget *(*find_by_id) (const Widget * w, unsigned long id);
+
+ /* *INDENT-OFF* */
+ cb_ret_t (*set_state) (Widget * w, widget_state_t state, gboolean enable);
+ /* *INDENT-ON* */
+ void (*destroy) (Widget * w);
+
+ const int *(*get_colors) (const Widget * w);
+};
+
+/* structure for label (caption) with hotkey, if original text does not contain
+ * hotkey, only start is valid and is equal to original text
+ * hotkey is defined as char*, but mc support only singlebyte hotkey
+ */
+typedef struct hotkey_t
+{
+ char *start; /* never NULL */
+ char *hotkey; /* can be NULL */
+ char *end; /* can be NULL */
+} hotkey_t;
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* create hotkey from text */
+hotkey_t hotkey_new (const char *text);
+/* release hotkey, free all mebers of hotkey_t */
+void hotkey_free (const hotkey_t hotkey);
+/* return width on terminal of hotkey */
+int hotkey_width (const hotkey_t hotkey);
+/* compare two hotkeys */
+gboolean hotkey_equal (const hotkey_t hotkey1, const hotkey_t hotkey2);
+/* draw hotkey of widget */
+void hotkey_draw (Widget * w, const hotkey_t hotkey, gboolean focused);
+/* get text of hotkey */
+char *hotkey_get_text (const hotkey_t hotkey);
+
+/* widget initialization */
+void widget_init (Widget * w, const WRect * r, widget_cb_fn callback,
+ widget_mouse_cb_fn mouse_callback);
+/* Default callback for widgets */
+cb_ret_t widget_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm,
+ void *data);
+void widget_set_options (Widget * w, widget_options_t options, gboolean enable);
+void widget_adjust_position (widget_pos_flags_t pos_flags, WRect * r);
+void widget_set_size (Widget * w, int y, int x, int lines, int cols);
+void widget_set_size_rect (Widget * w, WRect * r);
+/* select color for widget in dependance of state */
+void widget_selectcolor (Widget * w, gboolean focused, gboolean hotkey);
+cb_ret_t widget_draw (Widget * w);
+void widget_erase (Widget * w);
+void widget_set_visibility (Widget * w, gboolean make_visible);
+gboolean widget_is_active (const void *w);
+void widget_replace (Widget * old, Widget * new);
+gboolean widget_is_focusable (const Widget * w);
+void widget_select (Widget * w);
+void widget_set_bottom (Widget * w);
+
+long widget_lookup_key (Widget * w, int key);
+
+void widget_default_make_global (Widget * w, const WRect * delta);
+void widget_default_make_local (Widget * w, const WRect * delta);
+
+GList *widget_default_find (const Widget * w, const Widget * what);
+Widget *widget_default_find_by_type (const Widget * w, widget_cb_fn cb);
+Widget *widget_default_find_by_id (const Widget * w, unsigned long id);
+
+cb_ret_t widget_default_set_state (Widget * w, widget_state_t state, gboolean enable);
+
+void widget_default_destroy (Widget * w);
+
+/* get mouse pointer location within widget */
+Gpm_Event mouse_get_local (const Gpm_Event * global, const Widget * w);
+gboolean mouse_global_in_widget (const Gpm_Event * event, const Widget * w);
+
+/* --------------------------------------------------------------------------------------------- */
+/*** inline functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static inline cb_ret_t
+send_message (void *w, void *sender, widget_msg_t msg, int parm, void *data)
+{
+ cb_ret_t ret = MSG_NOT_HANDLED;
+
+#if 1
+ if (w != NULL) /* This must be always true, but... */
+#endif
+ ret = WIDGET (w)->callback (WIDGET (w), WIDGET (sender), msg, parm, data);
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether one or several option flags are set or not.
+ * @param w widget
+ * @param options widget option flags
+ *
+ * @return TRUE if all requested option flags are set, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_get_options (const Widget * w, widget_options_t options)
+{
+ return ((w->options & options) == options);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Check whether one or several state flags are set or not.
+ * @param w widget
+ * @param state widget state flags
+ *
+ * @return TRUE if all requested state flags are set, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_get_state (const Widget * w, widget_state_t state)
+{
+ return ((w->state & state) == state);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert widget coordinates from local (relative to owner) to global (relative to screen).
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_make_global (Widget * w)
+{
+ w->make_global (w, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Convert widget coordinates from global (relative to screen) to local (relative to owner).
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_make_local (Widget * w)
+{
+ w->make_local (w, NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Find widget.
+ *
+ * @param w widget
+ * @param what widget to find
+ *
+ * @return result of @w->find()
+ */
+
+static inline GList *
+widget_find (const Widget * w, const Widget * what)
+{
+ return w->find (w, what);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Find widget by widget type using widget callback.
+ *
+ * @param w widget
+ * @param cb widget callback
+ *
+ * @return result of @w->find_by_type()
+ */
+
+static inline Widget *
+widget_find_by_type (const Widget * w, widget_cb_fn cb)
+{
+ return w->find_by_type (w, cb);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Find widget by widget ID.
+ *
+ * @param w widget
+ * @param id widget ID
+ *
+ * @return result of @w->find_by_id()
+ */
+
+static inline Widget *
+widget_find_by_id (const Widget * w, unsigned long id)
+{
+ return w->find_by_id (w, id);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Modify state of widget.
+ *
+ * @param w widget
+ * @param state widget state flag to modify
+ * @param enable specifies whether to turn the flag on (TRUE) or off (FALSE).
+ * Only one flag per call can be modified.
+ * @return MSG_HANDLED if set was handled successfully, MSG_NOT_HANDLED otherwise.
+ */
+
+static inline cb_ret_t
+widget_set_state (Widget * w, widget_state_t state, gboolean enable)
+{
+ return w->set_state (w, state, enable);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy widget.
+ *
+ * @param w widget
+ */
+
+static inline void
+widget_destroy (Widget * w)
+{
+ w->destroy (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Get color colors of widget.
+ *
+ * @param w widget
+ * @return color colors
+ */
+static inline const int *
+widget_get_colors (const Widget * w)
+{
+ return w->get_colors (w);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update cursor position in the specified widget.
+ *
+ * @param w widget
+ *
+ * @return TRUE if cursor was updated successfully, FALSE otherwise
+ */
+
+static inline gboolean
+widget_update_cursor (Widget * w)
+{
+ return (send_message (w, NULL, MSG_CURSOR, 0, NULL) == MSG_HANDLED);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+widget_show (Widget * w)
+{
+ widget_set_visibility (w, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+widget_hide (Widget * w)
+{
+ widget_set_visibility (w, FALSE);
+}
+
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Check whether two widgets are overlapped or not.
+ * @param a 1st widget
+ * @param b 2nd widget
+ *
+ * @return TRUE if widgets are overlapped, FALSE otherwise.
+ */
+
+static inline gboolean
+widget_overlapped (const Widget * a, const Widget * b)
+{
+ return rects_are_overlapped (&a->rect, &b->rect);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#endif /* MC__WIDGET_INTERNAL_H */
diff --git a/lib/widget/wtools.c b/lib/widget/wtools.c
new file mode 100644
index 0000000..99f6f00
--- /dev/null
+++ b/lib/widget/wtools.c
@@ -0,0 +1,727 @@
+/*
+ Widget based utility functions.
+
+ Copyright (C) 1994-2022
+ Free Software Foundation, Inc.
+
+ Authors:
+ Miguel de Icaza, 1994, 1995, 1996
+ Radek Doulik, 1994, 1995
+ Jakub Jelinek, 1995
+ Andrej Borsenkow, 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 wtools.c
+ * \brief Source: widget based utility functions
+ */
+
+#include <config.h>
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "lib/global.h"
+#include "lib/tty/tty.h"
+#include "lib/tty/key.h" /* tty_getch() */
+#include "lib/strutil.h"
+#include "lib/util.h" /* tilde_expand() */
+#include "lib/widget.h"
+#include "lib/event.h" /* mc_event_raise() */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+/*** file scope type declarations ****************************************************************/
+
+/*** file scope variables ************************************************************************/
+
+static WDialog *last_query_dlg;
+
+static int sel_pos = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** default query callback, used to reposition query */
+
+static cb_ret_t
+query_default_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
+{
+ WDialog *h = DIALOG (w);
+
+ switch (msg)
+ {
+ case MSG_RESIZE:
+ if ((w->pos_flags & WPOS_CENTER) == 0)
+ {
+ WDialog *prev_dlg = NULL;
+ int ypos, xpos;
+ WRect r;
+
+ /* get dialog under h */
+ if (top_dlg != NULL)
+ {
+ if (top_dlg->data != (void *) h)
+ prev_dlg = DIALOG (top_dlg->data);
+ else
+ {
+ GList *p;
+
+ /* Top dialog is current if it is visible.
+ Get previous dialog in stack */
+ p = g_list_next (top_dlg);
+ if (p != NULL)
+ prev_dlg = DIALOG (p->data);
+ }
+ }
+
+ /* if previous dialog is not fullscreen'd -- overlap it */
+ if (prev_dlg == NULL || (WIDGET (prev_dlg)->pos_flags & WPOS_FULLSCREEN) != 0)
+ ypos = LINES / 3 - (w->rect.lines - 3) / 2;
+ else
+ ypos = WIDGET (prev_dlg)->rect.y + 2;
+
+ /* if dialog is too high, place it centered */
+ if (ypos + w->rect.lines < LINES / 2)
+ w->pos_flags |= WPOS_CENTER;
+
+ xpos = COLS / 2 - w->rect.cols / 2;
+
+ /* set position */
+ rect_init (&r, ypos, xpos, w->rect.lines, w->rect.cols);
+
+ return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
+ }
+ MC_FALLTHROUGH;
+
+ default:
+ return dlg_default_callback (w, sender, msg, parm, data);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Create message dialog */
+
+static WDialog *
+do_create_message (int flags, const char *title, const char *text)
+{
+ char *p;
+ WDialog *d;
+
+ /* Add empty lines before and after the message */
+ p = g_strconcat ("\n", text, "\n", (char *) NULL);
+ query_dialog (title, p, flags, 0);
+ d = last_query_dlg;
+
+ /* do resize before initing and running */
+ send_message (d, NULL, MSG_RESIZE, 0, NULL);
+
+ dlg_init (d);
+ g_free (p);
+
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show message dialog. Dismiss it when any key is pressed.
+ * Not safe to call from background.
+ */
+
+static void
+fg_message (int flags, const char *title, const char *text)
+{
+ WDialog *d;
+
+ d = do_create_message (flags, title, text);
+ tty_getch ();
+ dlg_run_done (d);
+ widget_destroy (WIDGET (d));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show message box from background */
+
+#ifdef ENABLE_BACKGROUND
+static void
+bg_message (int dummy, int *flags, char *title, const char *text)
+{
+ (void) dummy;
+ title = g_strconcat (_("Background process:"), " ", title, (char *) NULL);
+ fg_message (*flags, title, text);
+ g_free (title);
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+
+/**
+ * Show dialog, not background safe.
+ *
+ * If the arguments "header" and "text" should be translated,
+ * that MUST be done by the caller of fg_input_dialog_help().
+ *
+ * The argument "history_name" holds the name of a section
+ * in the history file. Data entered in the input field of
+ * the dialog box will be stored there.
+ *
+ */
+static char *
+fg_input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags)
+{
+ char *p_text;
+ char histname[64] = "inp|";
+ gboolean is_passwd = FALSE;
+ char *my_str;
+ int ret;
+
+ /* label text */
+ p_text = g_strstrip (g_strdup (text));
+
+ /* input history */
+ if (history_name != NULL && *history_name != '\0')
+ g_strlcpy (histname + 3, history_name, sizeof (histname) - 3);
+
+ /* The special value of def_text is used to identify password boxes
+ and hide characters with "*". Don't save passwords in history! */
+ if (def_text == INPUT_PASSWORD)
+ {
+ is_passwd = TRUE;
+ histname[3] = '\0';
+ def_text = "";
+ }
+
+ {
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (p_text, input_label_above, def_text, histname, &my_str,
+ NULL, is_passwd, strip_password, completion_flags),
+ QUICK_BUTTONS_OK_CANCEL,
+ QUICK_END
+ /* *INDENT-ON* */
+ };
+
+ WRect r = { -1, -1, 0, COLS / 2 };
+
+ quick_dialog_t qdlg = {
+ r, header, help,
+ quick_widgets, NULL, NULL
+ };
+
+ ret = quick_dialog (&qdlg);
+ }
+
+ g_free (p_text);
+
+ return (ret != B_CANCEL) ? my_str : NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+#ifdef ENABLE_BACKGROUND
+static int
+wtools_parent_call (void *routine, gpointer ctx, int argc, ...)
+{
+ ev_background_parent_call_t event_data;
+
+ event_data.routine = routine;
+ event_data.ctx = ctx;
+ event_data.argc = argc;
+ va_start (event_data.ap, argc);
+ mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call", (gpointer) & event_data);
+ va_end (event_data.ap);
+ return event_data.ret.i;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+wtools_parent_call_string (void *routine, int argc, ...)
+{
+ ev_background_parent_call_t event_data;
+
+ event_data.routine = routine;
+ event_data.argc = argc;
+ va_start (event_data.ap, argc);
+ mc_event_raise (MCEVENT_GROUP_CORE, "background_parent_call_string", (gpointer) & event_data);
+ va_end (event_data.ap);
+ return event_data.ret.s;
+}
+#endif /* ENABLE_BACKGROUND */
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/** Used to ask questions to the user */
+int
+query_dialog (const char *header, const char *text, int flags, int count, ...)
+{
+ va_list ap;
+ WDialog *query_dlg;
+ WGroup *g;
+ WButton *button;
+ int win_len = 0;
+ int i;
+ int result = -1;
+ int cols, lines;
+ const int *query_colors = (flags & D_ERROR) != 0 ? alarm_colors : dialog_colors;
+ widget_pos_flags_t pos_flags =
+ (flags & D_CENTER) != 0 ? (WPOS_CENTER | WPOS_TRYUP) : WPOS_KEEP_DEFAULT;
+
+ if (header == MSG_ERROR)
+ header = _("Error");
+
+ if (count > 0)
+ {
+ va_start (ap, count);
+ for (i = 0; i < count; i++)
+ {
+ char *cp = va_arg (ap, char *);
+
+ win_len += str_term_width1 (cp) + 6;
+ if (strchr (cp, '&') != NULL)
+ win_len--;
+ }
+ va_end (ap);
+ }
+
+ /* count coordinates */
+ str_msg_term_size (text, &lines, &cols);
+ cols = 6 + MAX (win_len, MAX (str_term_width1 (header), cols));
+ lines += 4 + (count > 0 ? 2 : 0);
+
+ /* prepare dialog */
+ query_dlg =
+ dlg_create (TRUE, 0, 0, lines, cols, pos_flags, FALSE, query_colors, query_default_callback,
+ NULL, "[QueryBox]", header);
+ g = GROUP (query_dlg);
+
+ if (count > 0)
+ {
+ WButton *defbutton = NULL;
+
+ group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
+ NULL);
+ group_add_widget (g, hline_new (lines - 4, -1, -1));
+
+ cols = (cols - win_len - 2) / 2 + 2;
+ va_start (ap, count);
+ for (i = 0; i < count; i++)
+ {
+ int xpos;
+ char *cur_name;
+
+ cur_name = va_arg (ap, char *);
+ xpos = str_term_width1 (cur_name) + 6;
+ if (strchr (cur_name, '&') != NULL)
+ xpos--;
+
+ button = button_new (lines - 3, cols, B_USER + i, NORMAL_BUTTON, cur_name, NULL);
+ group_add_widget (g, button);
+ cols += xpos;
+ if (i == sel_pos)
+ defbutton = button;
+ }
+ va_end (ap);
+
+ /* do resize before running and selecting any widget */
+ send_message (query_dlg, NULL, MSG_RESIZE, 0, NULL);
+
+ if (defbutton != NULL)
+ widget_select (WIDGET (defbutton));
+
+ /* run dialog and make result */
+ switch (dlg_run (query_dlg))
+ {
+ case B_CANCEL:
+ break;
+ default:
+ result = query_dlg->ret_value - B_USER;
+ }
+
+ /* free used memory */
+ widget_destroy (WIDGET (query_dlg));
+ }
+ else
+ {
+ group_add_widget_autopos (g, label_new (2, 3, text), WPOS_KEEP_TOP | WPOS_CENTER_HORZ,
+ NULL);
+ group_add_widget (g, button_new (0, 0, 0, HIDDEN_BUTTON, "-", NULL));
+ last_query_dlg = query_dlg;
+ }
+ sel_pos = 0;
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+query_set_sel (int new_sel)
+{
+ sel_pos = new_sel;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create message dialog. The caller must call dlg_run_done() and
+ * widget_destroy() to dismiss it. Not safe to call from background.
+ */
+
+WDialog *
+create_message (int flags, const char *title, const char *text, ...)
+{
+ va_list args;
+ WDialog *d;
+ char *p;
+
+ va_start (args, text);
+ p = g_strdup_vprintf (text, args);
+ va_end (args);
+
+ d = do_create_message (flags, title, p);
+ g_free (p);
+
+ return d;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show message box, background safe */
+
+void
+message (int flags, const char *title, const char *text, ...)
+{
+ char *p;
+ va_list ap;
+
+ va_start (ap, text);
+ p = g_strdup_vprintf (text, ap);
+ va_end (ap);
+
+ if (title == MSG_ERROR)
+ title = _("Error");
+
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ {
+ union
+ {
+ void *p;
+ void (*f) (int, int *, char *, const char *);
+ } func;
+ func.f = bg_message;
+
+ wtools_parent_call (func.p, NULL, 3, sizeof (flags), &flags, strlen (title), title,
+ strlen (p), p);
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ fg_message (flags, title, p);
+
+ g_free (p);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show error message box */
+
+gboolean
+mc_error_message (GError ** mcerror, int *code)
+{
+ if (mcerror == NULL || *mcerror == NULL)
+ return FALSE;
+
+ if ((*mcerror)->code == 0)
+ message (D_ERROR, MSG_ERROR, "%s", (*mcerror)->message);
+ else
+ message (D_ERROR, MSG_ERROR, _("%s (%d)"), (*mcerror)->message, (*mcerror)->code);
+
+ if (code != NULL)
+ *code = (*mcerror)->code;
+
+ g_error_free (*mcerror);
+ *mcerror = NULL;
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Show input dialog, background safe.
+ *
+ * If the arguments "header" and "text" should be translated,
+ * that MUST be done by the caller of these wrappers.
+ */
+
+char *
+input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags)
+{
+#ifdef ENABLE_BACKGROUND
+ if (mc_global.we_are_background)
+ {
+ union
+ {
+ void *p;
+ char *(*f) (const char *, const char *, const char *, const char *, const char *,
+ gboolean, input_complete_t);
+ } func;
+ func.f = fg_input_dialog_help;
+ return wtools_parent_call_string (func.p, 7,
+ strlen (header), header, strlen (text),
+ text, strlen (help), help,
+ strlen (history_name), history_name,
+ strlen (def_text), def_text,
+ sizeof (gboolean), strip_password,
+ sizeof (input_complete_t), completion_flags);
+ }
+ else
+#endif /* ENABLE_BACKGROUND */
+ return fg_input_dialog_help (header, text, help, history_name, def_text, strip_password,
+ completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Show input dialog with default help, background safe */
+
+char *
+input_dialog (const char *header, const char *text, const char *history_name, const char *def_text,
+ input_complete_t completion_flags)
+{
+ return input_dialog_help (header, text, "[Input Line Keys]", history_name, def_text, FALSE,
+ completion_flags);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+input_expand_dialog (const char *header, const char *text,
+ const char *history_name, const char *def_text,
+ input_complete_t completion_flags)
+{
+ char *result;
+
+ result = input_dialog (header, text, history_name, def_text, completion_flags);
+ if (result)
+ {
+ char *expanded;
+
+ expanded = tilde_expand (result);
+ g_free (result);
+ return expanded;
+ }
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Create status message window object and initialize it
+ *
+ * @param title window title
+ * @param delay initial delay to raise window in seconds
+ * @param init_cb callback to initialize user-defined part of status message
+ * @param update_cb callback to update of status message
+ * @param deinit_cb callback to deinitialize user-defined part of status message
+ *
+ * @return newly allocate status message window
+ */
+
+status_msg_t *
+status_msg_create (const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb)
+{
+ status_msg_t *sm;
+
+ sm = g_try_new (status_msg_t, 1);
+ status_msg_init (sm, title, delay, init_cb, update_cb, deinit_cb);
+
+ return sm;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Destroy status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+status_msg_destroy (status_msg_t * sm)
+{
+ status_msg_deinit (sm);
+ g_free (sm);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Initialize already created status message window object
+ *
+ * @param sm status message window object
+ * @param title window title
+ * @param delay initial delay to raise window in seconds
+ * @param init_cb callback to initialize user-defined part of status message
+ * @param update_cb callback to update of status message
+ * @param deinit_cb callback to deinitialize user-defined part of status message
+ */
+
+void
+status_msg_init (status_msg_t * sm, const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb)
+{
+ gint64 start;
+
+ /* repaint screen to remove previous finished dialog */
+ mc_refresh ();
+
+ start = g_get_monotonic_time ();
+
+ sm->dlg = dlg_create (TRUE, 0, 0, 7, MIN (MAX (40, COLS / 2), COLS), WPOS_CENTER, FALSE,
+ dialog_colors, NULL, NULL, NULL, title);
+ sm->start = start;
+ sm->delay = (gint64) (delay * G_USEC_PER_SEC);
+ sm->block = FALSE;
+
+ sm->init = init_cb;
+ sm->update = update_cb;
+ sm->deinit = deinit_cb;
+
+ if (sm->init != NULL)
+ sm->init (sm);
+
+ if (mc_time_elapsed (&start, sm->delay))
+ {
+ /* We will manage the dialog without any help, that's why we have to call dlg_init */
+ dlg_init (sm->dlg);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Deinitialize status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+status_msg_deinit (status_msg_t * sm)
+{
+ if (sm == NULL)
+ return;
+
+ if (sm->deinit != NULL)
+ sm->deinit (sm);
+
+ /* close and destroy dialog */
+ dlg_run_done (sm->dlg);
+ widget_destroy (WIDGET (sm->dlg));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Update status message window
+ *
+ * @param sm status message window object
+ *
+ * @return value of pressed key
+ */
+
+int
+status_msg_common_update (status_msg_t * sm)
+{
+ int c;
+ Gpm_Event event;
+
+ if (sm == NULL)
+ return B_ENTER;
+
+ /* This should not happen, but... */
+ if (sm->dlg == NULL)
+ return B_ENTER;
+
+ if (widget_get_state (WIDGET (sm->dlg), WST_CONSTRUCT))
+ {
+ /* dialog is not shown yet */
+
+ /* do not change sm->start */
+ gint64 start = sm->start;
+
+ if (mc_time_elapsed (&start, sm->delay))
+ dlg_init (sm->dlg);
+
+ return B_ENTER;
+ }
+
+ event.x = -1; /* Don't show the GPM cursor */
+ c = tty_get_event (&event, FALSE, sm->block);
+ if (c == EV_NONE)
+ return B_ENTER;
+
+ /* Reinitialize by non-B_CANCEL value to avoid old values
+ after events other than selecting a button */
+ sm->dlg->ret_value = B_ENTER;
+ dlg_process_event (sm->dlg, c, &event);
+
+ return sm->dlg->ret_value;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Callback to initialize already created simple status message window object
+ *
+ * @param sm status message window object
+ */
+
+void
+simple_status_msg_init_cb (status_msg_t * sm)
+{
+ simple_status_msg_t *ssm = SIMPLE_STATUS_MSG (sm);
+ Widget *wd = WIDGET (sm->dlg);
+ WGroup *wg = GROUP (sm->dlg);
+ WRect r;
+
+ const char *b_name = N_("&Abort");
+ int b_width;
+ int wd_width, y;
+ Widget *b;
+
+#ifdef ENABLE_NLS
+ b_name = _(b_name);
+#endif
+
+ b_width = str_term_width1 (b_name) + 4;
+ wd_width = MAX (wd->rect.cols, b_width + 6);
+
+ y = 2;
+ ssm->label = label_new (y++, 3, "");
+ group_add_widget_autopos (wg, ssm->label, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+ group_add_widget (wg, hline_new (y++, -1, -1));
+ b = WIDGET (button_new (y++, 3, B_CANCEL, NORMAL_BUTTON, b_name, NULL));
+ group_add_widget_autopos (wg, b, WPOS_KEEP_TOP | WPOS_CENTER_HORZ, NULL);
+
+ r = wd->rect;
+ r.lines = y + 2;
+ r.cols = wd_width;
+ widget_set_size_rect (wd, &r);
+}
+
+/* --------------------------------------------------------------------------------------------- */
diff --git a/lib/widget/wtools.h b/lib/widget/wtools.h
new file mode 100644
index 0000000..cd0bc32
--- /dev/null
+++ b/lib/widget/wtools.h
@@ -0,0 +1,100 @@
+/** \file wtools.h
+ * \brief Header: widget based utility functions
+ */
+
+#ifndef MC__WTOOLS_H
+#define MC__WTOOLS_H
+
+/*** typedefs(not structures) and defined constants **********************************************/
+
+/* Pass this as def_text to request a password */
+#define INPUT_PASSWORD ((char *) -1)
+
+/* Use this as header for message() - it expands to "Error" */
+#define MSG_ERROR ((char *) -1)
+
+typedef struct status_msg_t status_msg_t;
+#define STATUS_MSG(x) ((status_msg_t *)(x))
+
+typedef struct simple_status_msg_t simple_status_msg_t;
+#define SIMPLE_STATUS_MSG(x) ((simple_status_msg_t *)(x))
+
+typedef void (*status_msg_cb) (status_msg_t * sm);
+typedef int (*status_msg_update_cb) (status_msg_t * sm);
+
+/*** enums ***************************************************************************************/
+
+/* flags for message() and query_dialog() */
+enum
+{
+ D_NORMAL = 0,
+ D_ERROR = (1 << 0),
+ D_CENTER = (1 << 1)
+} /* dialog options */ ;
+
+/*** structures declarations (and typedefs of structures)*****************************************/
+
+/* Base class for status message of long-time operations.
+ Useful to show progress of long-time operaions and interrupt it. */
+
+struct status_msg_t
+{
+ WDialog *dlg; /* pointer to status message dialog */
+ gint64 start; /* start time in microseconds */
+ gint64 delay; /* delay before raise the 'dlg' in microseconds */
+ gboolean block; /* how to get event using tty_get_event() */
+
+ status_msg_cb init; /* callback to init derived classes */
+ status_msg_update_cb update; /* callback to update dlg */
+ status_msg_cb deinit; /* callback to deinit derived classes */
+};
+
+/* Simple status message with label and 'Abort' button */
+struct simple_status_msg_t
+{
+ status_msg_t status_msg; /* base class */
+
+ WLabel *label;
+};
+
+/*** global variables defined in .c file *********************************************************/
+
+/*** declarations of public functions ************************************************************/
+
+/* The input dialogs */
+char *input_dialog (const char *header, const char *text,
+ const char *history_name, const char *def_text,
+ input_complete_t completion_flags);
+char *input_dialog_help (const char *header, const char *text, const char *help,
+ const char *history_name, const char *def_text, gboolean strip_password,
+ input_complete_t completion_flags);
+char *input_expand_dialog (const char *header, const char *text, const char *history_name,
+ const char *def_text, input_complete_t completion_flags);
+
+int query_dialog (const char *header, const char *text, int flags, int count, ...);
+void query_set_sel (int new_sel);
+
+/* Create message box but don't dismiss it yet, not background safe */
+/* *INDENT-OFF* */
+WDialog *create_message (int flags, const char *title, const char *text, ...)
+ G_GNUC_PRINTF (3, 4);
+
+/* Show message box, background safe */
+void message (int flags, const char *title, const char *text, ...) G_GNUC_PRINTF (3, 4);
+/* *INDENT-ON* */
+
+gboolean mc_error_message (GError ** mcerror, int *code);
+
+status_msg_t *status_msg_create (const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb);
+void status_msg_destroy (status_msg_t * sm);
+void status_msg_init (status_msg_t * sm, const char *title, double delay, status_msg_cb init_cb,
+ status_msg_update_cb update_cb, status_msg_cb deinit_cb);
+void status_msg_deinit (status_msg_t * sm);
+int status_msg_common_update (status_msg_t * sm);
+
+void simple_status_msg_init_cb (status_msg_t * sm);
+
+/*** inline functions ****************************************************************************/
+
+#endif /* MC__WTOOLS_H */