#!/bin/bash
### Universal file viewer
### MochiMoppel 2017-09-13
{ #HEADER
TEMPDIR=/tmp/mm_view				# For temp files
CNFGDIR=$HOME/tmp/mm_view			# For user settings. ($HOME/tmp not saved to savefile. Eventually should be $HOME/.config/<appname>
[[ -d $TEMPDIR ]] || mkdir -p $TEMPDIR
[[ -d $CNFGDIR ]] || mkdir -p $CNFGDIR

### Files for TEMPDIR (*.tmp will be deleted after MMview session, other files remain alive until Puppy shutdown)
export SYMLINK=$TEMPDIR/symlink.tmp	# <input file> source for <edit> and  <pixmap> widgets.
export MSGFILE=$TEMPDIR/msgfile.tmp	# contains info text
export NB_PAGE=$TEMPDIR/nb_page.tmp	# contains notebook page
export PID_FFP=$TEMPDIR/pid_ffp.tmp	# contains PID of ffplay/avplay
export K_SHIFT=$TEMPDIR/k_shift.tmp	# shift key flag
export COMMAND=$TEMPDIR/command.tmp	# contains command string for vCMDLINE
export HISTORY=$TEMPDIR/history.txt	# contains command history
export TMPFILE=$TEMPDIR/tmpfile.tmp	# multipurpose tmp file
export STBFILE=$TEMPDIR/stbfile.tmp	# contains optional content for status bar
export FNDFILE=$TEMPDIR/fndfile.txt	# contains last used search pattern of Find dialog
export MMVICON=$TEMPDIR/mmvicon.svg	# MochiMoppel artwork
export D_TITLE=$TEMPDIR/d_title.tmp	# multi_dialog: title
export D_LABEL=$TEMPDIR/d_label.tmp	# multi_dialog: text label 
export D_DEFLT=$TEMPDIR/d_deflt.tmp	# multi_dialog: default text for entry widget

### Files for CNFGDIR
export LASTDIR=$CNFGDIR/lastdir		# contains last accessed directory
export LASTGEO=$CNFGDIR/lastgeo		# contains last geometry
export IMG_FIT=$CNFGDIR/img_fit		# image scale flag 
export BOXMODE=$CNFGDIR/boxmode		# vertical layout flag
export FNDREGX=$CNFGDIR/fndregx		# regex checkbox  status in Find dialog
export FNDCASE=$CNFGDIR/fndcase		# case  checkbox  status in Find dialog
export FNDWORD=$CNFGDIR/fndword		# word  checkbox  status in Find dialog
export FNDMARK=$CNFGDIR/fndmark		# Do not extract  checkbox status in Find dialog

### Window settings
DEFAULT_WIDTH=1000	# Default window width
DEFAULT_HIGHT=500	# Default window height
CHOOSER_WIDTH=500	# Effective only in horizontal layout (default 500)
CHOOSER_HIGHT=200	# Effective only in vertical layout (default 200)
MENUBAR_HIGHT=25	# Should not be set to 0 (disables shortcuts). Value 1 hides mbar but still allows access via F10 (default 25)
WINDOW_MARGIN=0		# Inner margin of window (default 0)
WIDGETSPACING=3		# Spaces between menu - chooser&viewer - statusbar  (default 3)
CENTERSPACING=0		# Space between chooser - viewer (default 0)
L_EDIT_MARGIN=5		# Left margin of the text viewer pane (default 5)
STATUS_MARGIN=5		# Margin around bottom box containing statusbar & close button (default 5)
X_AXIS_ADJUST=2		# Compensates for width of separator. Increase value to reduce width of scaled images (default 2)
Y_AXIS_ADJUST=47	# Compensates for height of status bar, image page text and separator. Increase value to reduce height of scaled images(default 47)  

### Determine utilities to be used
type -p ffprobe && PROBE=ffprobe   || { type -p avprobe && PROBE=avprobe || PROBE=false ;} ; export PROBE
type -p ffplay  && PLAY=ffplay     || { type -p avplay  && PLAY=avplay   || PLAY=false  ;} ; export PLAY
type -p xclip   && CLIP=xclip      || { type -p xsel    && CLIP=xsel     || CLIP=false  ;} ; export CLIP
type -p wmctrl  && WMCTRL=wmctrl   || WMCTRL=true                                          ; export WMCTRL

### Determine startup folder. Priorities: 1) Passed argument 2) Last used folder 3) Default '/'
if      [[ -e $1 ]];then
		[[ -d $1 ]]       && DEFAULT_DIRECTORY=$1            || DEFAULT_DIRECTORY=$(dirname "$1")
else    [[ -f $LASTDIR ]] && DEFAULT_DIRECTORY=$(< $LASTDIR) || DEFAULT_DIRECTORY=/ 
fi
### Determine startup modes
[[ -f $LASTGEO ]] && GEOMETRY=$(< $LASTGEO)    || GEOMETRY=${DEFAULT_WIDTH}x${DEFAULT_HIGHT}  # window size and position
[[ -f $BOXMODE ]] && LAYOUT_V=true BOX=v SEP=h || LAYOUT_V=false BOX=h SEP=v # orientation (horizontal or vertical)
[[ -f $IMG_FIT ]] && SCALE_IMAGE=true 		   || SCALE_IMAGE=false		     # image scaling

### Determine startup size of viewer pane (for image scaling) 
set -- ${GEOMETRY//[x+-]/ }													 # e.g. GEOMETRY=980x616+-232+592
	GEO_X=$1
	GEO_Y=$2
set --
if [[ $LAYOUT_V == true ]] ;then
	VIEWER_X=$((GEO_X - WINDOW_MARGIN *2))
	VIEWER_Y=$((GEO_Y - Y_AXIS_ADJUST - MENUBAR_HIGHT - CHOOSER_HIGHT - WINDOW_MARGIN - ( CENTERSPACING + WIDGETSPACING + STATUS_MARGIN) * 2 ))
else
	VIEWER_X=$((GEO_X - X_AXIS_ADJUST - CHOOSER_WIDTH - (WINDOW_MARGIN + CENTERSPACING) * 2))
	VIEWER_Y=$((GEO_Y - Y_AXIS_ADJUST - MENUBAR_HIGHT - (WINDOW_MARGIN + WIDGETSPACING + STATUS_MARGIN) * 2))
fi
export VIEWER_X VIEWER_Y

### Initialize files and prepare exitus
ln -fsn  /dev/null  $SYMLINK
echo  0	> $NB_PAGE
echo -n	> $TMPFILE
echo -n	> $COMMAND
#~ echo -n	> $CBOX_OP
trap "rm -r $TEMPDIR/*.tmp;busybox pkill -f \"$PLAY -nodisp\"" EXIT # pkill does not depend on PID. More reliable here

### Let MochiMoppel put on fashionable glasses
RNDCOLOR=$(printf %x $RANDOM$RANDOM$RANDOM);RNDCOLOR=${RNDCOLOR//[0-7]};RNDCOLOR=${RNDCOLOR::3}
echo "<svg width='48' height='48' viewBox='0 0 100 100'>
<style type='text/css' >
*			{fill:#fff;	stroke:#000;stroke-width:6}
polyline	{fill:none;stroke-linecap:round;}
circle		{fill:#$RNDCOLOR}
</style>
<!-- body,glasses,mouth,hair -->
<path		d='M-2,100 C0,-10 100,-10 102,100'/>
<circle		cx='27' cy='50' r='20'/>
<circle		cx='73' cy='50' r='20'/>
<polyline	points='65,80 70,80'/>
<polyline	points='40,5 50,15 70,10 60,17'/>
</svg>" > $MMVICON
}
{ #HELP AND TOOLTIPS
HLP1='
Enter a shell command\n\n
Placeholders:\n
\$@ = basename of selected file\n
\$PWD = directory of selected file\n
\$PWD/S@ = full path of selected file\n
To use placeholders literally add a backslash: \\\$@\n\n
Execute command by pressing Enter or F4\n
Close command box with Esc key or via menu\n
'
TT01=$'Executables:	Usage message (if available)\nDirectories:	Total size, incl. subdirectories\nArchives:		List of archive contents\nHTML files:	Text only, no tags'
TT02=
TT03=$'Execute command shown in command box\n(has no effect when command box is closed)'
TT04=$'Start/stop playing video/audio files\nPlay will start/stop immediately\nAs long as this menu item is checked, any selected video/audio files will play automatically'
TT05=$'Add line numbers to the content of the viewer pane\nThe selected file will not be modified'
TT06=$'Refresh the viewer pane with the default content'
TT07=$'Open a command box beneath file list to enter shell commands'
TT08=$'Open command box  with the previously entered command'
TT09=$'Close command box'
TT10=$'View history of Commandbox\nWhen the Commandbox has focus the history can also be retrieved by pressing Up arrow key'
TT11=$'Edit history of Commandbox ('$HISTORY')'
TT12=$'Clear history of Commandbox'
TT13=
TT14=
TT15=
TT16=
TT17=
TT18=$'Rename selected file\nAdding path will move the file\n\nNote: Unlike ROX-Filer MMview supports rel.paths,\ne.g. '\''../foo'\'' will move '\''foo'\'' to parent folder'
TT19=$'Make a duplicate (copy) of the selected file in the same folder. Name MUST be different.\nAdding path will copy the file\n\nNote: Unlike ROX-Filer MMview supports rel.paths,\ne.g. '\''../foo'\'' will copy '\''foo'\'' to parent folder'
TT20=$'Delete file shown in entry field\n\nNote: Wildcard characters supported, e.g. '\''*.mp3'\'' would deleted all mp3 files in the current folder'

}
function fill_statusbar {
	[[ -s $STBFILE ]]	&& { echo $(< $STBFILE) ; echo -n > $STBFILE; return ;}		# read and echo alternative content if exists, reset $STBFILE and go auto of here
	[[ -e $vCHOOSER  ]]	|| return													# vCHOOSER does not exist (anymore) after rename/delete operation
	[[ -L $vCHOOSER ]]	&& RP=$(realpath "$vCHOOSER") LNK='  ➡  ' || OP=$vCHOOSER	# link
	[[ -d ${OP}${RP} ]]	&& echo -n "${vCHOOSER}${LNK}${RP} │ x-directory" && return	# directory
	echo -n "${vCHOOSER}${LNK}${RP} │ $(file -bi "${OP}${RP}")"						# regular file
};export -f fill_statusbar
function view_instantly {
	if [[ -s $PID_FFP ]];then					# kill any ffplay/avplay that might still be running
		kill $(<$PID_FFP)
		echo -n > $PID_FFP
	fi
	echo -n "$vCHOOSER" | $CLIP -i              # mimic ROX-Filer and send selected item to PRIMARY buffer
	((    vNOTE    )) && echo  0 > $NB_PAGE     # if notebook page != 0 reset it to 0
	[[ -s $K_SHIFT ]] && echo -n > $K_SHIFT     # reset Shift key flag (mainly to recover from envoking search box with shift key and then using arrow keys to select next item...far fetched scenario?)
	[[ -s $MSGFILE ]] && echo -n > $MSGFILE   	# reset MSGFILE

	case $vSTATUSBAR in
	### DIRECTORIES AND EMPTY FILES ###
	*│*directory*)
		if  [[ $(echo "$vCHOOSER"/*) == "$vCHOOSER/*" ]];then
			MESSAGE=EMPTY
		elif [[ -L $vCHOOSER ]];then
			MESSAGE="SYMLINK to $(realpath "$vCHOOSER")" 
		elif [[ $vCHOOSER = /proc ]];then
			MESSAGE="VIRTUAL FILE SYSTEM" 
		else
			MESSAGE=$(busybox timeout -t1 du -hs  "$vCHOOSER")
			if (($?));then
				MESSAGE="This takes too long. Instead press F1 for directory info"
			else
				MESSAGE="${MESSAGE%%[[:blank:]]*}		(disk usage)"
			fi 
		fi
		;;
	*│*empty*)
		if [[ "$vCHOOSER" =~ (^/proc/) ]] ;then         # /proc contains seemingly empty virtual files that can't be read by linking them
				MESSAGE=$(< "$vCHOOSER")
		else    vCHOOSER=/dev/null
		fi
		;;

	### IMAGES (must precede text patterns as xpm and svg should not be viewed as text)
	*'.xpm '*)
		echo 1 > $NB_PAGE
		;;
	*│*image/svg*|*'.svg '*)
		if [[ $vMENU_SCALE == true ]];then
			 echo -n "SVG image, scaled to fit viewer" > $TMPFILE
			 echo  2 > $NB_PAGE
		else echo  1 > $NB_PAGE
		fi
		;;
	*│*image*)
		if [[ $vMENU_SCALE == true ]] ;then
			EXIV=$(exiv2 "$vCHOOSER" 2>/dev/null)
			set -- ${EXIV#*Image size}					# '      : 1600 x 1200 ...'
			IMAGE_X=$2									# '1600'
			IMAGE_Y=$4									# '1200'
			set --
			if (( IMAGE_X > VIEWER_X || IMAGE_Y > VIEWER_Y ));then
				 SX=$(( 100 * VIEWER_X / IMAGE_X))		# scale in % ;Unrounded integer only, but good enough
				 SY=$(( 100 * VIEWER_Y / IMAGE_Y))
				 SCALE=$((SY < SX ? SY : SX))			# SCALE = SX or SY, whichever is smaller
				 echo -n "Scaled to ${SCALE}% of ${IMAGE_X}x${IMAGE_Y}" > $TMPFILE
				 echo 2 > $NB_PAGE
			else echo 1 > $NB_PAGE
			fi
		else echo 1 > $NB_PAGE
		fi
		;;

	### TEXT ###
	*charset=iso*|*charset=*unknown*)
		CHARSET=${vSTATUSBAR#*charset=}
		[[ $CHARSET = *unknown* ]] && CHARSET=iso-8859-1    # Seems to work best
		OOPS=$(iconv -f $CHARSET -t UTF-8 -o $MSGFILE -c "$vCHOOSER" 2>&1>/dev/null )
		if [[ $OOPS ]] ;then
			MESSAGE=$'Something went wrong.\n\nMaybe "iconv" is not installed or could not convert charset '"$CHARSET"$'\nHere is the error message:\n\n'"$OOPS"
		else ln -fsn $MSGFILE  $SYMLINK; return
		fi
		;;
	 *│*text*|*│*script*|*│*xml*)
		[[ "$vCHOOSER" =~ (^/sys/) ]] && MESSAGE=$(< "$vCHOOSER" )
		;;

	### AUDIO/VIDEO ###
	*│*video*|*'.mkv '*|*'.mpg '*)
		if [[ $vMENU_AUDIOPLAY == true && $PLAY != false ]] ;then
			$PLAY -x 320 -y 180 -i "$vCHOOSER"  &
			echo -n $! > $PID_FFP
 		fi
		MESSAGE=$($PROBE "$vCHOOSER" 2>&1)
		[[ $MESSAGE ]] || MESSAGE='Display of video specs requires "ffprobe" or "avprobe"'
		;; 
	*│*audio*|*'.mp3 '*|*'.ogg '*|*'.wma '*)
		if [[ $vMENU_AUDIOPLAY == true && $PLAY != false ]] ;then
			$PLAY -nodisp -i "$vCHOOSER"  &
			echo -n $! > $PID_FFP
		fi
		if [[ $PROBE != false ]];then
			FFP=$($PROBE "$vCHOOSER" 2>&1)
			DUR=Duration:${FFP#*Duration:}
			ID3=${FFP##*Metadata:$'\n'}
			if [[ "$ID3" = "$FFP" ]] ;then          # No Metadata found
				ID3=""
			else
				EN_SPACE=$'\xe2\x80\x82'			# Unicode U+2002 EN SPACE (" ")
				ID3=${ID3/$DUR}                     # Strip trailing $DUR
				ID3=${ID3// /$EN_SPACE}             # replace SPACE with EN SPACE
				ID3=${ID3//$EN_SPACE:/$'\t:'}       # replace single EN SPACE followed by colon with a tab
			fi
			MESSAGE=$(echo -n "$DUR"$'\n\n'"$ID3" | sed "s/^[[:blank:]]*//")
		else
			MESSAGE='Display of audio specs requires "ffprobe" or "avprobe"'
		fi
		;;    

	### EXECUTABLES ###
	*│*executable*)
		if [[ $(grep -i "usage:" "$vCHOOSER") ]] ;then
				MESSAGE=$'\n\tUsage info may exist. Try F1'
		else    vCHOOSER=/dev/null
		fi
		;;

	### ARCHIVES ###
	*│*zip*|*│*xz*|*'.7z '*|*'.deb '*|*'.iso '*)      # .tar, .pet , .txz, .gz , .gzip, .bzip2, .zip, .7z, .deb, .iso, initrd* 
		MESSAGE=$'\n\tTry F1 to view archive contents'
		;;

	### EVERYTHING ELSE ###
	*)	vCHOOSER=/dev/null ;;
	esac

	if [[ "$MESSAGE" ]];then
		 echo "$MESSAGE"    > $MSGFILE
		 ln -fsn  $MSGFILE    $SYMLINK
	else ln -fsn "$vCHOOSER"  $SYMLINK
	fi
};export -f view_instantly
function view_on_demand {
   (( vNOTE )) && echo  0 > $NB_PAGE 					    # set to notebook page 0
	FIGURESPACE=$'\xe2\x80\x87'								# Unicode U+2007
   case $vSTATUSBAR in 

#------------ DISPLAY OUTPUT OF <FILENAME> --help COMMAND
	*│*executable*)
		grep -iq "usage:" "$vCHOOSER" || return             # exit function if 'usage:' not found
		for ARG in --help -help;do 
			urxvt -g 1x1+0+0  -e bash -c "                  # open console in minimum size in upper left corner to reduce flicker
			{ sleep 0.5; kill \$$ ;} &                      # let console commit suicide after 0.5sec.
			\"$vCHOOSER\" $ARG  &> $MSGFILE                 # redirect all output to MSGFILE
			"
			grep -iqm1 "usage:" $MSGFILE && break           # leave loop if output contains 'usage:' 
		done
		;;
#------------ LIST DIRECTORY SUBFOLDERS
	*│*directory*) 	
		[[ -L $vCHOOSER || $vCHOOSER == /proc || $(< $MSGFILE) == EMPTY ]] && return	# get out of here if SYMLINK, VIRTUAL FILE SYSTEM or EMPTY
		DU_ALL=$(du -h --max-depth=1 "$vCHOOSER"|sort -k2)	# vCHOOSER on top, followed by subs; not yet sorted by size; du --max-depth more compatible than -d
		DU_TOP="${DU_ALL%%[[:blank:]]*}		(disk usage)"	# first line (=vCHOOSER)
		CNTSUB=${DU_ALL//[^$'\n']}							# strip everything but newlines
		CNTSUB=${#CNTSUB}									# No. of newlines; zero in case of no subdirectories
		if (( CNTSUB == 0 )) ;then
			echo "$DU_TOP" $'\n   └ (no subdirectories)' > $MSGFILE
		else
			echo "$DU_TOP" > $MSGFILE
			DU_SUB=${DU_ALL#*$'\n'}
			echo -n "$DU_SUB" | sort -hr |awk '
			{
				$1=substr("───── "$1,length($1))
				gsub(/\/.*\//,"")
				print " └"$1" "$2"  "substr($0,index($0,$3))
			}' >> $MSGFILE
		fi
		;;
#------------ LIST ARCHIVE CONTENT
	*'/initrd'*)											# contributed by SFR (http://www.murga-linux.com/puppy/viewtopic.php?p=950405#950405)
		COMPRESSION=${vSTATUSBAR##*x-}						# '/initrd.gz │ application/x-gzip; charset=binary' => 'gzip; charset=binary' 
		COMPRESSION=${COMPRESSION##*application/}			# '/initrd.gz | application/gzip; charset=binary' => 'gzip; charset=binary' 
		COMPRESSION=${COMPRESSION%;*}						# 'gzip; charset=binary' => 'gzip' 
		case $COMPRESSION in 
		  gzip)		CMND='gunzip  -c -d'	;; 
		  bzip2)	CMND='bunzip2 -c -d'	;; 
		  xz|lzma)	CMND='unxz    -c -d'	;; 
		  lz4)		CMND='unlz4   -c -d'	;; 
		  *)		CMND='cat'				;;				# not compressed 
		esac 
		$CMND "$vCHOOSER" | busybox cpio -ivt | awk '
		{
			$1=toupper(substr($1,1,1))					# 1st char = filetype; uppercase helps to align proportional fonts
			if ($1=="-") $1="F"							# - = regular file
			$3=substr("       "$3,length($3))			# padd with " " (Unicode U+2007 e2 80 87 FIGURE SPACE); max diplayed value: 99M
			print $1,$3"\t"$4"\t"substr($0,index($0,$6))	# filetype \t size \t date \t filename
		}'  > $MSGFILE
		;; 
#------------
	*│*gzip*|*│*xz*|*│*bzip2*)								# extensions .tar, .pet , .txz, .gz , .gzip, .bzip2
		ARCH_OUTPUT=$(tar -vtf "$vCHOOSER")
		if [[ $ARCH_OUTPUT ]] ;then 						# output: tar extracted something, no output: most likely gzip contains to tarball, but in some cases of gzipped text files tar returned no output and exit code 0, so checking for exit codes does not help here
			echo "$ARCH_OUTPUT" | awk '
			{
				if (substr($6,length($6),1) == "/" ) next	# ignore folders
				$3=substr("       "$3,length($3))			# padd with " " (Unicode U+2007 e2 80 87 FIGURE SPACE); max diplayed value: 99M
				print $3,$4,$5"\t"$6						# size date time \t filename
			}'  > $MSGFILE
		else												# alternative if tar failed
			case $vSTATUSBAR in
			*│*gzip*)	list_archive 'gzip  -cd' ;;			# display content of gzip compressed text files (e.g. .gnumeric)
			*│*xz*)		list_archive 'xz    -cd' ;;			# display content of xz compressed text files
			*│*bzip2*)	list_archive 'bzip2 -cd' ;;			# display content of bzip2 compressed text files
			esac
		fi
		;;
#------------
	*'.deb '*)												# extension .deb
		dpkg-deb -c "$vCHOOSER" | awk '
		{
			if (substr($6,length($6),1) == "/" ) next	# ignore folders
			$3=substr("       "$3,length($3))			# padd with " " (Unicode U+2007 e2 80 87 FIGURE SPACE); max diplayed value: 99M
			print $3,$4,$5"\t"$6						# size date time \t filename
		}'  > $MSGFILE
		;;
#------------
	*'.iso '*)	list_archive 'iso-info --no-header -l' 	;;	# extension .iso
#------------
	*│*/zip*)	list_archive 'unzip -l' 				;;	# extensions .zip, .xlsx (MS Excel) etc.
#------------
	*'.7z '*)	list_archive '7z l'						;;	# extension .7z
	
#------------ HTML, ABIWORD, NOTECASE
	*│*html*|*'.html │'*|*'.htm │'*|*'.ncd │'*|*'.abw │'*)
	busybox tr -s '\r\n' '\x01' < "$vCHOOSER" | sed -r '	# change Unix/Win/Mac line endings into hex01 markers, i.e put whole file content on a single line
	s/^.*<\/head>//Ig					# delete document declarations and the <head> section
	s/<style[^<]*<\/style>//Ig			# delete <style> sections
	s/<script/\x0&/Ig					# add NULL markers before <script>
	s/\x0<script[^\x0]*<\/script>//Ig	# delete <script> sections (unlike <style> sections <script> sections may contain < characters, making it difficult to match the content, hence the unlikely NULL character)
	s/&nbsp;|\t/ /g						# replace &nbsp and tabs with normal space
	s/  +/ /g							# reduce consecutive spaces (bad habit of NYT) to 1 space
	s/>[ \x01]+</></g					# delete all spaces or newlines between tags (if they are the only characters that separate the tags)
	s/<metadata.*<\/metadata>//			# delete <metadata> section (Abiword)
	s/<d[ >][^<]*<\/d>//g				# delete <d> sections (Abiword. <data> may contain multiple <d>, so greedy match will delete until last </d>. Good! Deleting whole <data> section instead might conflict with HTML5 <data>)
	s/<div|<p[ >]/\x01\x01&/Ig			# precede <div> and <p> with blank line (does not match <pre>,<progress>,<param>,<picture>)
	s/<h[1-6]/\x01\x01● & /Ig			# precede <h> with blank line and bullet
	s/<dt/\x01\x01◆ &/Ig				# precede <dt> (Notecase likes them) with lineend markers and bullet
	s/<li/\x01• &/Ig						# put <li> on new line and add bullet (unfortunately not possible to determine if list should be vertical or horizontal)
	s/<\/h[1-9]|<dd|<tr|<br/\x01</Ig	# precede </h <dd <tr <br with lineend marker, i.e. put them always on a new line
	s/<[^>]*>|-->//g					# delete all tags and --> (a leftover in Gtkdialog references)
	s/&[lr]?d?quot?;/"/g					# convert &ldquo; &rdquo; &quot;
	s/&lt;/</g							# convert &lt;
	s/&gt;/>/g							# convert &gt;
	s/&amp;/\&/g							# convert &amp;
	s/&copy;/©/g							# convert &copy;
	s/^[ \x01]+//						# delete first line if blank
	s/\x01 +/\x01/g						# delete all spaces at line start
	s/[•◆●] *\x01//g					# delete lines with orphaned bullets
	s/\x01\x01\x01+/\x01\x01/g			# reduce consecutive blank lines to 1 line
	s/\x01/\n/g							# change lineend markers back into newlines
' | busybox fold -sw 90 > $MSGFILE
	;;
#------------ EVERYTHING ELSE
	*)	NOMATCH=1 ;;
	esac
	((NOMATCH)) && echo false || ln -fsn $MSGFILE $SYMLINK	# 'false' will prevent refresh
};export -f view_on_demand
function get_geometry {
		XWININFO=$(xwininfo -id $1)
		XWIN_ARR=($(awk '/^ *Abs|^ *Rel|^ *Width|^ *Height/ { print $NF }' <<< "$XWININFO"))
		WINDOW_W=${XWIN_ARR[4]}
		WINDOW_H=${XWIN_ARR[5]}
		WINDOW_X=$(( ${XWIN_ARR[0]} - ${XWIN_ARR[2]} ))
		WINDOW_Y=$(( ${XWIN_ARR[1]} - ${XWIN_ARR[3]} ))
		GEOMETRY=${WINDOW_W}x${WINDOW_H}+${WINDOW_X}+${WINDOW_Y}
		echo -n $GEOMETRY
};export -f get_geometry
function save_settings {
		ACTWINID=$(xprop -root _NET_ACTIVE_WINDOW)
		ACTWINID=${ACTWINID##* }
		get_geometry $ACTWINID  > $LASTGEO
		echo -n $(dirname "$vCHOOSER") > $LASTDIR
};export -f save_settings
function open_editor   {
	echo -n > $K_SHIFT					# reset shiftkey flag
	if [[ ! -f "$vCHOOSER" ]] ;then		# editor should open only regular files ...
		return
	elif [[ ! $vSTATUSBAR =~ (x-empty|│.*script|│.*text|│.*ascii|│.*utf-8) ]];then
		Xdialog --title MMview --yesno "Does not appear to be a text file\nTry to open anyway?" x
		(($?)) && return
	fi
	defaulttexteditor "$vCHOOSER"
};export -f open_editor
function execute_in_console {
		[[ $vSTATUSBAR =~  (x-executable|x-shellscript) ]] &&
		urxvt -title "$vCHOOSER" -e bash -c "\"$vCHOOSER\";echo;read -n1 -p \"Press any key to close\"" &
};export -f execute_in_console
function list_archive {
		TOOLNAME=${@%% *}
		if type -p "$TOOLNAME" ;then
			case $vSTATUSBAR in
			 *'.iso '*|*│*/zip*|*'.7z '*)	$@ "$vCHOOSER" | sed "s/ /$FIGURESPACE/g" > $MSGFILE ;;
			 *)								$@ "$vCHOOSER" > $MSGFILE ;;
			esac
		else echo "Requires command line tool $TOOLNAME"	> $MSGFILE
		fi
		ln -fsn $MSGFILE $SYMLINK
};export -f list_archive
function find_again {
	[[ -s $MSGFILE ]] && SOURCEFILE=$MSGFILE || SOURCEFILE=$vCHOOSER
	FIND="$(< $FNDFILE)"
	[[ $FIND ]] || return 1
	[[ $vCHB_REGX = true ]] && OPT_REGX=-E TXT=Regex || OPT_REGX=-F TXT=Text
	[[ $vCHB_CASE = true ]] && OPT_CASE=             || OPT_CASE=-i
	[[ $vCHB_WORD = true ]] && OPT_WORD=-w           || OPT_WORD=
	CNT=$(grep $OPT_REGX $OPT_CASE $OPT_WORD -o "$FIND" "$SOURCEFILE" |wc -l)
	((CNT>1)) && PL=es || { ((CNT<1)) && CNT=no ;}
	echo "$TXT Search for \"$FIND\" found $CNT match$PL" > $STBFILE
	((CNT)) || return	# avoid blank viewer pane
	if [[ $vCHB_MARK = true ]];then
		ESC=$(echo -e "\033")
		grep --color=always -C999 $OPT_REGX $OPT_CASE  $OPT_WORD -n "$FIND" "$SOURCEFILE" | sed "s/$ESC\[01;31m$ESC\[K/▊/g;s/$ESC\[[0-9;]*m$ESC\[K//g" > $TMPFILE
	else
		grep $OPT_REGX $OPT_CASE  $OPT_WORD  -n "$FIND" "$SOURCEFILE" > $TMPFILE
	fi
	ln -fsn $TMPFILE $SYMLINK
};export -f find_again
function add_linenumbers {
	[[ -s $MSGFILE ]] && SOURCEFILE=$MSGFILE || SOURCEFILE=$vCHOOSER
	grep -n '.*' "$SOURCEFILE"  > $TMPFILE
	ln -fsn $TMPFILE $SYMLINK
};export -f add_linenumbers
function exec_dialog {
	if [[ $1 ]];then							# 1 argument = function called from Menu
			printf "$1"							> $D_TITLE
		case $1 in
		Rename|Duplicate)
			printf "<b><big>${1^^}</big></b>"	> $D_LABEL
			printf "${vCHOOSER##*/}"			> $D_DEFLT
			;;
		Delete)
			[[ -d $vCHOOSER ]] && D=" DIRECTORY"
			printf "<span color='white' bgcolor='red'><b><big> ${1^^}$D </big></b></span>"	> $D_LABEL
			printf "${vCHOOSER##*/}"			> $D_DEFLT
			;;
		Find)
			printf "<b><big>${1^^}</big></b>"	> $D_LABEL
			printf "$(<$FNDFILE)"				> $D_DEFLT
			;;
		esac
	else										# no argumet = function called from dialog's OK button action
		case $(<$D_TITLE) in
Rename|Duplicate|Delete)cd "$(dirname "$vCHOOSER")"					;;&
			Rename)		mv -uv	"$vCHOOSER"	"$vD_ENTRY"	&> $STBFILE	;;
			Duplicate)	cp -ruv	"$vCHOOSER"	"$vD_ENTRY"	&> $STBFILE	;;
			Delete)		eval rm -rv			"$vD_ENTRY"	&> $STBFILE	;;
			Find)		printf "$vD_ENTRY"	> $FNDFILE
						printf $vCHB_REGX	> $FNDREGX
						printf $vCHB_CASE	> $FNDCASE
						printf $vCHB_WORD	> $FNDWORD
						printf $vCHB_MARK	> $FNDMARK
						find_again
						;;
		esac
	fi
}; export -f exec_dialog
function exec_shell_command {
		cd "$(dirname "$vCHOOSER")"
		FCMD=${vCMDLINE//\$@/$(basename "$vCHOOSER")}	# $@ = Basename of currently selected file/directory
		OUT=$(bash -c "$FCMD" 2>&1)
		RET=$?
		[[ $RET     = 0 ]] && [[ "$(tail -n1 $HISTORY)" != "$vCMDLINE" ]] && echo "$vCMDLINE" >> $HISTORY
		[[ $RET$OUT = 0 ]] && return
		echo -n "$OUT" > $MSGFILE
		ln -fsn $MSGFILE $SYMLINK
};export -f exec_shell_command
{ #MULTI_DIALOG
export MULTI_DIALOG='
<window modal="true" width-request="0" height-request="0" window-position="3" image-name="'$MMVICON'" resizable="false">
<vbox>
	<text xalign="0" use-markup="true"><input file>'$D_LABEL'</input></text>
	<entry activates_default="true">
		<input file>'$D_DEFLT'</input>
		<variable>vD_ENTRY</variable>
	</entry>
	<vbox>
		<checkbox label="Use regular expressions"><variable>vCHB_REGX</variable></checkbox>
		<checkbox label="Match case">             <variable>vCHB_CASE</variable></checkbox>
		<checkbox label="Match only whole word">  <variable>vCHB_WORD</variable></checkbox>
		<hseparator></hseparator>
		<checkbox label="Use inline markers (do not extract matching lines)"><variable>vCHB_MARK</variable></checkbox>
		<variable>vD_CHECKBOXES</variable>
	</vbox>
	<hbox>
		<button label="gtk-cancel" use-stock="true" width-request="180">
			<variable>vD_BTN_CANCEL</variable>
			<action>closewindow:MULTI_DIALOG</action>
			<action>presentwindow:MAIN_DIALOG</action>
		</button>
		<button label="gtk-ok" use-stock="true" width-request="180" can-default="true" has-default="true">
			<action condition="command_is_true(exec_dialog)">activate:vMACRO_VIEWTEXT</action>
			<action condition="command_is_true([[ -s $STBFILE ]] && echo true  )">refresh:vSTATUSBAR</action>
			<action condition="visible_is_true(vD_CHECKBOXES)">activate:vMENU_FINDAGAIN</action>
			<action>closewindow:MULTI_DIALOG</action>
			<action>presentwindow:MAIN_DIALOG</action>
		</button>
	</hbox>
</vbox>
<variable>MULTI_DIALOG</variable>
<input file>'$D_TITLE'</input>
<action signal="show">{ sleep 1;$WMCTRL -r :ACTIVE: -b add,above ;}&</action>
<action signal="show" condition="command_is_true([[ $(<$D_TITLE) != Find ]] && echo true )">hide:vD_CHECKBOXES</action>
<action signal="show" condition="command_is_true([[ $(<$D_TITLE) != Find ]] && echo true )">break:</action>
<action signal="show" condition="command_is_true( echo $(<$FNDREGX) )">activate:vCHB_REGX</action>
<action signal="show" condition="command_is_true( echo $(<$FNDCASE) )">activate:vCHB_CASE</action>
<action signal="show" condition="command_is_true( echo $(<$FNDWORD) )">activate:vCHB_WORD</action>
<action signal="show" condition="command_is_true( echo $(<$FNDMARK) )">activate:vCHB_MARK</action>
<action signal="key-press-event" condition="command_is_true([ $KEY_SYM = Escape ] && echo true )">closewindow:MULTI_DIALOG</action>
<action signal="key-press-event" condition="command_is_true([ $KEY_SYM = Escape ] && echo true )">presentwindow:MAIN_DIALOG</action>
</window>'
}
{ #MACROS
MACROS='
<menu visible="false">
	<menuitem>
		<variable export="false">vMACRO_REFRESH</variable>
		<action>refresh:vSTATUSBAR</action>
		<action>view_instantly</action>
		<action condition="command_is_true([[ $(<$NB_PAGE) != $vNOTE ]] && echo true )">refresh:vNOTE</action>
		<action condition="command_is_true([[ $vNOTE       == 0      ]] && echo true )">refresh:vEDIT</action>
		<action condition="command_is_true([[ $vNOTE       == 1      ]] && echo true )">refresh:vPM01</action>
		<action condition="command_is_true([[ $vNOTE       == 2      ]] && echo true )">refresh:vPM02</action>
		<action condition="command_is_true([[ $vNOTE       == 2      ]] && echo true )">refresh:vTXT2</action>
	</menuitem>
	<menuitem>
		<variable export="false">vMACRO_WRITECMD</variable>
		<action>grabfocus:vCMDLINE</action>
		<action>clear:vCMDLINE</action>
		<action>refresh:vCMDLINE</action>
	</menuitem>
	<menuitem>
		<variable export="false">vMACRO_OPEN_COMMANDBOX</variable>
		<action>disable:vMENU_CMDBOX_OPEN</action>
		<action>enable:vMENU_F4</action>
		<action>show:vCOMMANDBOX</action>
	</menuitem>
	<menuitem>
		<variable export="false">vMACRO_VIEWTEXT</variable>
		<action>echo -n 0 > $NB_PAGE</action>
		<action>refresh:vEDIT</action>
		<action>refresh:vNOTE</action>
	</menuitem>
</menu>
'
}
{ #MENU
MENU='
<vbox space-expand="false">
	<menubar  height-request="'$MENUBAR_HIGHT'">
		'$MACROS'
		<menu label="File">
			<menuitem label="Open in default editor Shift+click" accel-key="0x45" accel-mods="4" stock-id="gtk-edit">
				<action>open_editor chooser</action>
			</menuitem>
			<menuitem label="Run executable file in console" accel-key="0xffc2" accel-mods="4" stock-id="gtk-execute">
				<action>execute_in_console</action>
			</menuitem>
			<menuitem label="Show in ROX            MiddleClick" accel-key="0x72" accel-mods="5"  stock-id="gtk-jump-to">
				<action>rox -s "$vCHOOSER"</action>
			</menuitem>
			<menuitem label="Rename" accel-key="0x72" accel-mods="4"stock-id="gtk-strikethrough" tooltip-text="'$TT18'">
				<action>exec_dialog Rename</action>
				<action>launch:MULTI_DIALOG</action>
			</menuitem>
			<menuitem label="Duplicate (copy)" accel-key="0x64" accel-mods="4"stock-id="gtk-copy" tooltip-text="'$TT19'">
				<action>exec_dialog Duplicate</action>
				<action>launch:MULTI_DIALOG</action>
			</menuitem>
			<menuitem label="Delete" accel-key="0x78" accel-mods="4" stock-id="gtk-delete" tooltip-text="'$TT20'">
				<action>exec_dialog Delete</action>
				<action>launch:MULTI_DIALOG</action>
			</menuitem>
			<menuitemseparator></menuitemseparator>
			<menuitem label="Exit" accel-key="0x071" accel-mods="4" stock-id="gtk-quit">
				<action>activate:vBTN_CLOSE</action>
			</menuitem>
		</menu>
		<menu label="Search">
			<menuitem label="Find ..." accel-key="0x46" accel-mods="4"  stock-id="gtk-find">
				<action>exec_dialog Find</action>
				<action>launch:MULTI_DIALOG</action>
			</menuitem>
			<menuitem label="Find again" accel-key="0xffc0" accel-mods="0">
				<variable export="false">vMENU_FINDAGAIN</variable>
				<action>find_again</action>
				<action>refresh:vSTATUSBAR</action>
				<action>activate:vMACRO_VIEWTEXT</action>
			</menuitem>
		</menu>
		<menu label="View">
			<menuitem label="Additional info (see tooltip)" tooltip-text="'$TT01'" accel-key="0xffbe" accel-mods="0" image-name="'$MMVICON'">
				<width>16</width>
				<action condition="command_is_false(view_on_demand)">break:</action>
				<action>refresh:vEDIT</action> 
				<action>refresh:vNOTE</action> 
			</menuitem>
			<menuitem label="Refresh" accel-key="0xffc2" accel-mods="0" stock-id="gtk-refresh"  tooltip-text="'$TT06'">
				<variable export="false">vMENU_REFRESH</variable>
				<action>echo 0 > $NB_PAGE</action>
				<action>activate:vMACRO_REFRESH</action>
			</menuitem>
			<menuitem label="Play video/audio files" checkbox="'$AUTO_PLAY_A'" accel-key="0x7a" accel-mods="4" tooltip-text="'$TT04'">
				<variable>vMENU_AUDIOPLAY</variable>
			   <action>view_instantly</action> 
			</menuitem>
			<menuitem label="Show line numbers" accel-key="0xffbf" accel-mods="0"  tooltip-text="'$TT05'">
				<action>add_linenumbers</action>
				<action>activate:vMACRO_VIEWTEXT</action>
			</menuitem>
		</menu>
	   <menu label="Commandbox">
			<menuitem label="Open"  accel-key="0x21"   accel-mods="1" tooltip-text="'$TT07'">
				<variable>vMENU_CMDBOX_OPEN</variable>
				<action>activate:vMACRO_OPEN_COMMANDBOX</action>
				<action>echo " \"\$@\" " > $COMMAND</action>
				<action>activate:vMACRO_WRITECMD</action>
			</menuitem>
			<menuitem label="Open with last command"  accel-key="0x21" accel-mods="5" tooltip-text="'$TT08'">
				<action>activate:vMACRO_OPEN_COMMANDBOX</action>
				<action>tail -n1 $HISTORY > $COMMAND</action>
				<action>activate:vMACRO_WRITECMD</action>
			</menuitem>
			<menuitem label="Close" accel-key="0xff1b" accel-mods="0" stock-id="gtk-close" tooltip-text="'$TT09'">
				<action>hide:vCOMMANDBOX</action>
				<action>enable:vMENU_CMDBOX_OPEN</action>
				<action>disable:vMENU_F4</action>
				<action>activate:vMENU_REFRESH</action>
			</menuitem>
			<menuitem label="Run command" accel-key="0xffc1" accel-mods="0" stock-id="gtk-execute" tooltip-text="'$TT03'">
				<variable export="false">vMENU_F4</variable>
				<action>exec_shell_command</action>
				<action>activate:vMACRO_VIEWTEXT</action>
			</menuitem>
			<menuitemseparator></menuitemseparator>
			<menuitem label="View Command History" image-name="'$MMVICON'" tooltip-text="'$TT10'">
				<variable>vMENU_VIEWHISTORY</variable>
				<width>16</width>
				<action>ln -fsn $HISTORY $SYMLINK</action>
				<action>activate:vMACRO_VIEWTEXT</action>
			</menuitem>
			<menuitem label="Edit Command History" stock-id="gtk-edit" tooltip-text="'$TT11'">
				<action>defaulttexteditor $HISTORY</action>
			</menuitem>
			<menuitem label="Clear Command History" stock-id="gtk-delete" tooltip-text="'$TT12'">
				<action>echo -n > $HISTORY</action>
			</menuitem>
	   </menu>
		<menu label="Preferences">
			<menuitem label="Fit images to viewer size" checkbox="'$SCALE_IMAGE'">
				<variable>vMENU_SCALE</variable>
				<action>if true >   $IMG_FIT</action>
				<action>if false rm $IMG_FIT</action>
				<action>activate:vMACRO_REFRESH</action>
			</menuitem>
			<menuitem label="Vertical layout (viewer below chooser)" checkbox="'$LAYOUT_V'" >
 				<action>if true >   $BOXMODE</action>
				<action>if false rm $BOXMODE</action>
				<action>save_settings; unset -v vCMDLINE; '$(realpath "$0")' &</action>
				<action>exit:</action>
			</menuitem>
		</menu>
	</menubar>
</vbox>
'
}
{ #MAIN_DIALOG
echo '
<window title="MMview" allow-shrink="true" margin="'$WINDOW_MARGIN'" image-name="'$MMVICON'">
<vbox space-expand="true" space-fill="true"  spacing="'$WIDGETSPACING'">
'"$MENU"' 
	<'$BOX'box space-expand="true"  spacing="'$CENTERSPACING'">
		<chooser width-request="'$CHOOSER_WIDTH'" height-request="'$CHOOSER_HIGHT'" space-expand="false" space-fill="true">
			<variable>
"vCHOOSER"
			</variable>
			<default>"'$DEFAULT_DIRECTORY'"</default>
			<action>"[[ -s $PID_FFP ]] && { kill $(<$PID_FFP); echo -n > $PID_FFP ;} "</action> 
			<action>rox "$vCHOOSER"</action> 
			<action when="update-preview">activate:vMACRO_REFRESH</action>
			<action signal="button-release-event">[[ $PTR_BTN == 1 && -s $K_SHIFT ]] && open_editor chooser &</action> 
			<action signal="button-release-event">[[ $PTR_BTN == 2                ]] && rox -s  "$vCHOOSER" &</action> 
			<action signal="key-press-event">     [[ $KEY_SYM == Shift_[LR]       ]] && echo    > $K_SHIFT</action>
			<action signal="key-release-event">   [[ $KEY_SYM == Shift_[LR]       ]] && echo -n > $K_SHIFT</action>
		</chooser>
		<'$SEP'separator space-expand="false"></'$SEP'separator>
		<notebook space-fill="true" space-expand="true" show-tabs="false" show-border="false">
			<vbox>
				<edit editable="true" left-margin="'$L_EDIT_MARGIN'">
					<variable export="false">
"vEDIT"
					</variable>
					<input file>'$SYMLINK'</input>
				</edit>
			</vbox>
			<vbox>
				<text label="Original size" xalign="2"></text>
				<vbox scrollable="true">
					<pixmap space-expand="true">
						<variable export="false">
"vPM01"
						</variable>
						<input file>'$SYMLINK'</input>
					</pixmap>
				</vbox>
			</vbox>
			<vbox>
				<text xalign="2">
					<variable export="false">
"vTXT2"
					</variable>
					<input file>"'$TMPFILE'"</input>
				</text>
				<pixmap space-expand="true" space-fill="true">
					<variable export="false">
"vPM02"
					</variable>
					<width>'$VIEWER_X'</width>
					<height>'$VIEWER_Y'</height>
					<input file>'$SYMLINK'</input>
				</pixmap>
			</vbox>
			<variable>
"vNOTE"
			</variable>
			<input file>'$NB_PAGE'</input>
		</notebook>
	</'$BOX'box>
	<hbox space-expand="false" visible="false" spacing="0">
		<button relief="2"><input file stock="gtk-help"></input>
			<action>Xdialog --title "MMview" --left --msgbox "'$HLP1'" x &</action>
		</button>
		<text space-expand="false" xalign="0" label="Shell: "></text> 
		<entry space-fill="true" space-expand="true" secondary-icon-stock="gtk-clear" secondary-icon-tooltip-text="Clear command line">
			<variable>
"vCMDLINE"
			</variable>
			<input file>"'$COMMAND'"</input>
			<action signal="focus-out-event">echo -n > $K_SHIFT  # Menu shortcut Shift! or Shift used in search box sets shift flag but does not set it back</action>
			<action signal="key-press-event" condition="command_is_true([[ $KEY_VAL == 0xff52 ]] && echo true )">activate:vMENU_VIEWHISTORY</action>
			<action signal="secondary-icon-release">clear:vCMDLINE</action>
			<action signal="activate">activate:vMENU_F4</action>
		</entry>
		<variable export="false">
"vCOMMANDBOX"
		</variable>
	</hbox>
	<hbox space-expand="false" margin="'$STATUS_MARGIN'" visible="true"> 
		<entry  space-expand="true">
			<variable>
"vSTATUSBAR"
			</variable>
			<input>fill_statusbar</input>
		</entry>
		<button label="   Close   ">
			<variable export="false">
"vBTN_CLOSE"
			</variable>
			<action>save_settings</action>
			<action>exit:OK</action>
		</button>
	</hbox>
</vbox>
<variable>MAIN_DIALOG</variable>
<action signal="show">disable:vMENU_F4</action>
<<action signal="key-press-event" condition="command_is_true([[ $KEY_SYM == Escape ]] && echo true )">busybox pkill -f "$PLAY -nodisp" && > $PID_FFP</action>
</window>
' | gtkdialog -G $GEOMETRY -s
}
