diff options
Diffstat (limited to '')
50 files changed, 14993 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..9353dec --- /dev/null +++ b/lib/widget/Makefile.in @@ -0,0 +1,843 @@ +# 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/ax_check_pcre2.m4 \ + $(top_srcdir)/m4.include/dx_doxygen.m4 \ + $(top_srcdir)/m4.include/ax_require_defined.m4 \ + $(top_srcdir)/m4.include/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4.include/ax_append_flag.m4 \ + $(top_srcdir)/m4.include/ax_append_compile_flags.m4 \ + $(top_srcdir)/m4.include/mc-cflags.m4 \ + $(top_srcdir)/m4.include/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4.include/mc-check-search-type.m4 \ + $(top_srcdir)/m4.include/mc-get-fs-info.m4 \ + $(top_srcdir)/m4.include/mc-with-x.m4 \ + $(top_srcdir)/m4.include/mc-use-termcap.m4 \ + $(top_srcdir)/m4.include/mc-with-screen.m4 \ + $(top_srcdir)/m4.include/mc-with-screen-ncurses.m4 \ + $(top_srcdir)/m4.include/mc-with-screen-slang.m4 \ + $(top_srcdir)/m4.include/mc-with-internal-edit.m4 \ + $(top_srcdir)/m4.include/mc-subshell.m4 \ + $(top_srcdir)/m4.include/mc-background.m4 \ + $(top_srcdir)/m4.include/mc-ext2fs-attr.m4 \ + $(top_srcdir)/m4.include/mc-glib.m4 \ + $(top_srcdir)/m4.include/mc-vfs.m4 \ + $(top_srcdir)/m4.include/vfs/rpc.m4 \ + $(top_srcdir)/m4.include/vfs/socket.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-extfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-sfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-ftp.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-sftp.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-fish.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-undelfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-tarfs.m4 \ + $(top_srcdir)/m4.include/vfs/mc-vfs-cpiofs.m4 \ + $(top_srcdir)/m4.include/mc-version.m4 \ + $(top_srcdir)/m4.include/mc-tests.m4 \ + $(top_srcdir)/m4.include/mc-i18n.m4 \ + $(top_srcdir)/m4.include/mc-assert.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +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_CFLAGS = @PCRE_CFLAGS@ +PCRE_LIBS = @PCRE_LIBS@ +PERL = @PERL@ +PERL_FOR_BUILD = @PERL_FOR_BUILD@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +PYTHON = @PYTHON@ +RANLIB = @RANLIB@ +RUBY = @RUBY@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SLANG_CFLAGS = @SLANG_CFLAGS@ +SLANG_LIBS = @SLANG_LIBS@ +STRIP = @STRIP@ +TESTS_LDFLAGS = @TESTS_LDFLAGS@ +UNZIP = @UNZIP@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +X11_WWW = @X11_WWW@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +XMKMF = @XMKMF@ +X_CFLAGS = @X_CFLAGS@ +X_EXTRA_LIBS = @X_EXTRA_LIBS@ +X_LIBS = @X_LIBS@ +X_PRE_LIBS = @X_PRE_LIBS@ +ZIP = @ZIP@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +noinst_LTLIBRARIES = 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..1965dee --- /dev/null +++ b/lib/widget/background.c @@ -0,0 +1,126 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 2020-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..9f0bfa5 --- /dev/null +++ b/lib/widget/button.c @@ -0,0 +1,284 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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_close (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..4522000 --- /dev/null +++ b/lib/widget/buttonbar.c @@ -0,0 +1,290 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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; + w->options |= WOP_WANT_HOTKEY; + + 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 * +buttonbar_find (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..af9249c --- /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 *buttonbar_find (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..63c55e3 --- /dev/null +++ b/lib/widget/check.c @@ -0,0 +1,182 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..959cbf9 --- /dev/null +++ b/lib/widget/dialog-switch.c @@ -0,0 +1,409 @@ +/* + Support of multiply editors and viewers. + + Original idea and code: Oleg "Olegarch" Konovalov <olegarch@linuxinside.com> + + Copyright (C) 2009-2023 + 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 ****************************************************************************/ + +/* 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; + +/* 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; + +WDialog *filemanager = NULL; + +/*** file scope macro definitions ****************************************************************/ + +/*** file scope type declarations ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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 = listbox_window_new (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 = listbox_run_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 +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 +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..5163095 --- /dev/null +++ b/lib/widget/dialog-switch.h @@ -0,0 +1,44 @@ + +#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 GList *top_dlg; + +extern gboolean fast_refresh; + +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); + +/* Redraw all dialogs */ +void do_refresh (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..3ab2191 --- /dev/null +++ b/lib/widget/dialog.c @@ -0,0 +1,626 @@ +/* + Dialog box features module for the Midnight Commander + + Copyright (C) 1994-2023 + Free Software Foundation, Inc. + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** \file 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; + +/* A hook list for idle events */ +hook_t *idle_hook = NULL; + +/* 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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_close (h); + break; + case CK_Cancel: + h->ret_value = B_CANCEL; + dlg_close (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); + send_message (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_close (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.p = 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 +dlg_close (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); + widget_draw (wh); + + 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..93c4638 --- /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 */ + char *event_group; /* Name of event group for this dialog */ + Widget *bg; /* WFrame or WBackground */ + + /* Data can be passed to dialog */ + union + { + void *p; + int i; + } data; + + 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; + +/* A hook list for idle events */ +extern hook_t *idle_hook; + +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_close (WDialog * h); + +/* --------------------------------------------------------------------------------------------- */ +/*** inline functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +#endif /* MC__DIALOG_H */ diff --git a/lib/widget/frame.c b/lib/widget/frame.c new file mode 100644 index 0000000..31127ab --- /dev/null +++ b/lib/widget/frame.c @@ -0,0 +1,164 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 2020-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..5eebb11 --- /dev/null +++ b/lib/widget/gauge.c @@ -0,0 +1,178 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..eb6ba1e --- /dev/null +++ b/lib/widget/group.c @@ -0,0 +1,970 @@ +/* + Widget group features module for the Midnight Commander + + Copyright (C) 2020-2023 + 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; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..49cf7b0 --- /dev/null +++ b/lib/widget/groupbox.c @@ -0,0 +1,136 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..8197db8 --- /dev/null +++ b/lib/widget/history.c @@ -0,0 +1,302 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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.p == NULL) + return MSG_NOT_HANDLED; + + data = (history_dlg_data *) dlg_head->data.p; + + 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_close (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.p = &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_set_current (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_set_current (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..03f764f --- /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 string 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..73e261a --- /dev/null +++ b/lib/widget/hline.c @@ -0,0 +1,194 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..3a67b49 --- /dev/null +++ b/lib/widget/input.c @@ -0,0 +1,1323 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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 position 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..bd7aa26 --- /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 otherwise. + */ +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..94a4c3b --- /dev/null +++ b/lib/widget/input_complete.c @@ -0,0 +1,1484 @@ +/* + Input line filename/username/hostname/variable/command completion. + (Let mc type for you...) + + Copyright (C) 1995-2023 + 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; + +/*** forward declarations (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); + +/*** 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 ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +#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); + 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_close (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_close (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_close (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_set_current (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_close (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_set_current (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_close (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..5a04a0f --- /dev/null +++ b/lib/widget/label.c @@ -0,0 +1,201 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..47d7f8b --- /dev/null +++ b/lib/widget/listbox-window.c @@ -0,0 +1,176 @@ +/* + Widget based utility functions. + + Copyright (C) 1994-2023 + 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 * +listbox_window_centered_new (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 * +listbox_window_new (int lines, int cols, const char *title, const char *help) +{ + return listbox_window_centered_new (-1, -1, lines, cols, title, help); +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Returns the number of the item selected */ +int +listbox_run (Listbox * l) +{ + int val = -1; + + if (dlg_run (l->dlg) != B_CANCEL) + val = l->list->current; + widget_destroy (WIDGET (l->dlg)); + g_free (l); + return val; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + * A variant of listbox_run() 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 * +listbox_run_with_data (Listbox * l, const void *select) +{ + void *val = NULL; + + if (select != NULL) + listbox_set_current (l->list, listbox_search_data (l->list, select)); + + if (dlg_run (l->dlg) != B_CANCEL) + { + WLEntry *e; + + e = listbox_get_nth_entry (l->list, l->list->current); + 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..b9bb1e8 --- /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 *listbox_window_centered_new (int center_y, int center_x, int lines, int cols, + const char *title, const char *help); +Listbox *listbox_window_new (int lines, int cols, const char *title, const char *help); +int listbox_run (Listbox * l); +void *listbox_run_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..9f25487 --- /dev/null +++ b/lib/widget/listbox.c @@ -0,0 +1,832 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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->current * (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->current && 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->current + 1 < g_queue_get_length (l->list)) + listbox_set_current (l, l->current + 1); + else if (wrap) + listbox_select_first (l); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +listbox_fwd_n (WListbox * l, int n) +{ + listbox_set_current (l, MIN (l->current + n, LISTBOX_LAST (l))); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +listbox_back (WListbox * l, gboolean wrap) +{ + if (!listbox_is_empty (l)) + { + if (l->current > 0) + listbox_set_current (l, l->current - 1); + else if (wrap) + listbox_select_last (l); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +listbox_back_n (WListbox * l, int n) +{ + listbox_set_current (l, MAX (l->current - 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->current + 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_set_current (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_add_entry (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->current), e); + break; + + case LISTBOX_APPEND_AFTER: + g_queue_insert_after (l->list, g_queue_peek_nth_link (l->list, (guint) l->current), 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_close (h); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +listbox_run_hotkey (WListbox * l, int pos) +{ + listbox_set_current (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_current; + + old_current = l->current; + + switch (msg) + { + case MSG_MOUSE_DOWN: + widget_select (w); + listbox_set_current (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_set_current (l, listbox_y_pos (l, event->y)); + break; + + case MSG_MOUSE_CLICK: + /* We don't call listbox_set_current() 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->current != old_current) + 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->current = 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); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* Select the first entry and scrolls the list to the top */ +void +listbox_select_first (WListbox * l) +{ + l->current = 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->current = DOZ (length, 1); + l->top = DOZ (length, lines); +} + +/* --------------------------------------------------------------------------------------------- */ + +void +listbox_set_current (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->current = dest; + if (!top_seen) + l->top = l->current; + else + { + int lines = WIDGET (l)->rect.lines; + + if (l->current - l->top >= lines) + l->top = l->current - lines + 1; + } + return; + } + } + + /* If we are unable to find it, set decent values */ + l->current = 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_entry (l, l->current); + + ok = (e != NULL); + + if (string != NULL) + *string = ok ? e->text : NULL; + + if (extra != NULL) + *extra = ok ? e->data : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +WLEntry * +listbox_get_nth_entry (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->current); + 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->current = 0; + else if (l->current >= length) + l->current = 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->current = 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_add_entry (l, entry, pos); + + return entry->text; +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/widget/listbox.h b/lib/widget/listbox.h new file mode 100644 index 0000000..0a62dfd --- /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 top; /* The first element displayed */ + int current; /* The current 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_set_current (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_entry (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..4a30c02 --- /dev/null +++ b/lib/widget/menu.c @@ -0,0 +1,1092 @@ +/* + Pulldown menu code + + Copyright (C) 1994-2023 + 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 current; /* pointer to current menu entry */ + char *help_node; +}; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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->current)); + 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->current)); + 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->current ? 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->current == (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->current))->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->current == 0) + menubar->current = g_list_length (menubar->menu) - 1; + else + menubar->current--; + menubar_draw (menubar); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menubar_right (WMenuBar * menubar) +{ + menubar_remove (menubar); + menubar->current = (menubar->current + 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->current = selected; + menubar_draw (menubar); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menubar_execute (WMenuBar * menubar) +{ + const menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current)); + const menu_entry_t *entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current)); + + if ((entry != NULL) && (entry->command != CK_IgnoreKey)) + { + Widget *w = WIDGET (menubar); + + mc_global.widget.is_right = (menubar->current != 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->current)); + const unsigned int len = g_list_length (menu->entries); + menu_entry_t *entry; + + menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR); + + do + { + menu->current = (menu->current + 1) % len; + entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current)); + } + while ((entry == NULL) || (entry->command == CK_IgnoreKey)); + + menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menubar_up (WMenuBar * menubar) +{ + menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current)); + const unsigned int len = g_list_length (menu->entries); + menu_entry_t *entry; + + menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR); + + do + { + if (menu->current == 0) + menu->current = len - 1; + else + menu->current--; + entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current)); + } + while ((entry == NULL) || (entry->command == CK_IgnoreKey)); + + menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR); +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +menubar_first (WMenuBar * menubar) +{ + if (menubar->is_dropped) + { + menu_t *menu = MENU (g_list_nth_data (menubar->menu, menubar->current)); + + if (menu->current == 0) + return; + + menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR); + + menu->current = 0; + + while (TRUE) + { + menu_entry_t *entry; + + entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current)); + + if ((entry == NULL) || (entry->command == CK_IgnoreKey)) + menu->current++; + else + break; + } + + menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR); + } + else + { + menubar->current = 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->current)); + const unsigned int len = g_list_length (menu->entries); + menu_entry_t *entry; + + if (menu->current == len - 1) + return; + + menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR); + + menu->current = len; + + do + { + menu->current--; + entry = MENUENTRY (g_list_nth_data (menu->entries, menu->current)); + } + while ((entry == NULL) || (entry->command == CK_IgnoreKey)); + + menubar_paint_idx (menubar, menu->current, MENU_SELECTED_COLOR); + } + else + { + menubar->current = 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->current); + + 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->current = 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->current))->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->current); + 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->current); + 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) menu_free); +} + +/* --------------------------------------------------------------------------------------------- */ + +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->current)); + 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->current)); + entry = MENUENTRY (g_list_nth_data (menu->entries, y)); + + if (entry != NULL && entry->command != CK_IgnoreKey) + { + menubar_paint_idx (menubar, menu->current, MENU_ENTRY_COLOR); + menu->current = y; + menubar_paint_idx (menubar, menu->current, 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_new (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 * +menu_new (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->current = 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 +menu_free (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; + 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->current = 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 * +menubar_find (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->current = (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..ce2cebe --- /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_new() 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 current; /* Current 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_new (const char *name, long command); +void menu_entry_free (menu_entry_t * me); + +menu_t *menu_new (const char *name, GList * entries, const char *help_node); +void menu_set_name (menu_t * menu, const char *name); +void menu_free (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 *menubar_find (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..15ad5f5 --- /dev/null +++ b/lib/widget/mouse.c @@ -0,0 +1,227 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 2016-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..35f5d68 --- /dev/null +++ b/lib/widget/quick.c @@ -0,0 +1,626 @@ +/* + Widget based utility functions. + + Copyright (C) 1994-2023 + 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; + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..8a722c1 --- /dev/null +++ b/lib/widget/quick.h @@ -0,0 +1,354 @@ +/** \file quick.h + * \brief Header: quick dialog engine + */ + +#ifndef MC__QUICK_H +#define MC__QUICK_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..8fb52d8 --- /dev/null +++ b/lib/widget/radio.c @@ -0,0 +1,251 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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..34ae8b0 --- /dev/null +++ b/lib/widget/rect.c @@ -0,0 +1,253 @@ +/* Rectangular class for Midnight Commander widgets + + Copyright (C) 2020-2023 + 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 height + * @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 height + * @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 height + * @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 height + * Positive value means move up and increase height. + * Negative value means move down and decrease height. + * @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..821b7b3 --- /dev/null +++ b/lib/widget/widget-common.c @@ -0,0 +1,905 @@ +/* + Widgets for the Midnight Commander + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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 (const 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->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 object 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 (const 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 */ + switch (state) + { + case WST_CONSTRUCT: + w->state &= ~(WST_ACTIVE | WST_SUSPENDED | WST_CLOSED); + break; + case WST_ACTIVE: + w->state &= ~(WST_CONSTRUCT | WST_SUSPENDED | WST_CLOSED); + break; + case WST_SUSPENDED: + w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_CLOSED); + break; + case WST_CLOSED: + w->state &= ~(WST_CONSTRUCT | WST_ACTIVE | WST_SUSPENDED); + break; + default: + break; + } + } + + 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..19acec1 --- /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_COMMON_H +#define MC__WIDGET_COMMON_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 (const 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 dependence of state */ +void widget_selectcolor (const 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_COMMON_H */ diff --git a/lib/widget/wtools.c b/lib/widget/wtools.c new file mode 100644 index 0000000..a4af4b5 --- /dev/null +++ b/lib/widget/wtools.c @@ -0,0 +1,729 @@ +/* + Widget based utility functions. + + Copyright (C) 1994-2023 + 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 ****************************************************************/ + +/*** forward declarations (file scope functions) *************************************************/ + +/*** 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, NULL); + 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..73c56ca --- /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 operations 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 */ |