#!/bin/ash
# Fatdog Customisable Control Panel
# Copyright (C) James Budiono 2012, 2013
#
# License: GNU GPL Version 3 or later
#
# Note: the bits in Global Inits, which is part of GTK-server bash init code
#       is (C) Peter van Eerten. Everything else is (C) James Budiono
#
# Version 1.0 - initial release
# Version 1.1 - add desktop and icon files in $XDG_DATA_HOME
#             - add reading configuration from control-panel-directory
#               (in addition to control-panel-applet file)
#
# Applets are defined in /etc/control-panel-applets and $HOME/.fatdog/control-panel-applets
# and in files inside /etc/control-panel-applets.dir and $HOME/.fatdog/control-panel-applets.dir
#
# These two files will be sourced when the application starts.
# Anything shell variables that starts with "TABx" will be interpreted as applet definition.
# The must contain TABx="tab-name|applet1 applet2 applet3".
# "x" can be anything (in version 1.0, it has to be a number from 1 to 100). Tabs are sorted with sort -V
#
# if called parameters, it will use the specified control file instead of system defined ones as above
# $1-apptitle, $* - applet definition files; see also --options below
#
#140110 internationalisation by L18L, make use of translated Name and Comment in *.desktop files
#180929 Jake SFR - local performance tweaks
#181129 step - options --start-tab= --one-tab=

# std localisation stanza
export TEXTDOMAIN=fatdog
. gettext.sh
# performance tweak - use "C" if there is no localisation
#! [ -e $TEXTDOMAINDIR/${LANG%.*}/LC_MESSAGES/$TEXTDOMAIN.mo ] &&
#! [ -e $TEXTDOMAINDIR/${LANG%_*}/LC_MESSAGES/$TEXTDOMAIN.mo ] && LANG=C
# can't use this as LANG=C will be propagated to child applets and screw them up

### configuration variables
APPTITLE="$(gettext 'Fatdog64 Control Panel')"
APPICON="/usr/share/pixmaps/midi-icons/controlpanel48.png"
APPICON2="/usr/share/pixmaps/midi-icons/go48.png"
DESKTOP_FILES_DIRS="/usr/share/applications /usr/local/share/applications $XDG_DATA_HOME/applications"
ICON_DIRS="/usr/share/pixmaps/ /usr/share/midi-icons/ /usr/share/mini-icons/ /usr/share/icons/ $XDG_DATA_HOME/icons"
ICON_DISPLAY_SIZE="32 32"	# in pixels, widh x height
ICON_WIDTH="100"			# in pixels, max size of each icon cell
WINDOW_SIZE="600 300"		# in pixels, startup window width x height
HIDPI_WINDOW_SIZE="1100 600"

SYSTEM_APPLETS=/etc/control-panel-applets
SYSTEM_APPLETS_DIR=${SYSTEM_APPLETS}.dir
USER_APPLETS=$FATDOG_STATE_DIR/control-panel-applets
USER_APPLETS_DIR=${USER_APPLETS}.dir

### default applets, can be overriden by the applet files
# no default applets
#TAB1="Desktop|wallpaper zarfy fatdog-event-manager"
#TAB2="System|BootManager-configure-bootup fatdog-set-timezone"

### runtime variables
GDK_TYPE_PIXBUF= MAIN_WINDOW= NOTEBOOK=
APPLETS_COUNT=0		# no of applets initialised

# tree model
T_TITLE=0 T_PIXBUF=1 T_TOOLTIP=2 T_EXEC=3

#---------------------------------------------------------- Global Inits
### global variables for proper GTK-server
GTK= NULL="NULL"
PIPE=/tmp/gtk.bash.$$
trap 'stop-gtk-server -fifo $PIPE; exit' HUP TERM INT # stop GTK-server in case of an error

### start GTK-server
gtk-server -fifo=$PIPE &
while [ ! -p $PIPE ]; do sleep 1; continue; done

### GTK-server helper functions
# Assignment function
define() { $2 $3 $4 $5 $6 $7 $8 $9; eval $1=\'"$GTK"\'; }

# Communicate with GTK-server
gtk()
{
/bin/echo "$@" > $PIPE
read GTK < $PIPE
}

#---------------------------------------------------------- GUI realization

# add GTK+ API not defined in gtk-server.cfg
add_missing_gtk_api() {
	gtk gtk_server_define gdk_pixbuf_get_type NONE INT 0
	gtk gtk_server_define gtk_window_new NONE WIDGET 1 INT	# bug fix

	gtk gtk_server_define gtk_icon_view_new_with_model item-activated WIDGET 1 WIDGET
	gtk gtk_server_define gtk_icon_view_set_text_column NONE NONE 2 WIDGET INT
	gtk gtk_server_define gtk_icon_view_set_pixbuf_column NONE NONE 2 WIDGET INT
	gtk gtk_server_define gtk_icon_view_set_tooltip_column NONE NONE 2 WIDGET INT
	gtk gtk_server_define gtk_icon_view_get_cursor NONE NONE 3 WIDGET PTR_LONG NULL
	gtk gtk_server_define gtk_icon_view_set_item_width NONE NONE 2 WIDGET INT

	# hack for getting unlimited string length and leak-free operation with gtk_tree_get_model
	# see below
	gtk gtk_server_define strcat NONE STRING 2 POINTER STRING
	gtk gtk_server_define memcpy NONE NONE 3 PTR_LONG POINTER INT
}

### helper - leak-free, unlimited string length from gtk_tree_model_get with unpatched gtk-server
# works with gtk-server 2.3.1 and 2.4.5
# $1-tree $2-iter $3-index $4-result name
tree_model_get_string() {
	local PSTR # pointer to string
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT PTR_LONG INT # ensure correct definition
	define PSTR gtk gtk_tree_model_get $1 $2 $3 NULL -1 # PSTR --> *buf
	
	if [ $PSTR -ne 0 ]; then
		# with newer gtk-server 2.4.4 onwards you can use this
		#define $4 gtk gtk_server_string_from_pointer $PSTR 
		define $4 gtk strcat $PSTR \"\" # get string point by address --> **buf
		gtk free $PSTR # this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}

load_applets() {
	local lang iter tab applets tabname liststore iconview scrollwindow label title comment cmd icon xpid numname tabnum tabEN
	Xdialog --title "$APPTITLE" --no-buttons --infobox "$(eval_gettext '$APPTITLE is Loading applets, please wait ...')" 0 0 1000000 &
	xpid=$!

	# redefine for our purpose
	gtk gtk_server_define gtk_list_store_new NONE WIDGET 5 INT INT INT INT INT
	gtk gtk_server_define gtk_list_store_set NONE NONE 11 WIDGET WIDGET INT STRING INT WIDGET INT STRING INT STRING INT

	define iter gtk gtk_server_opaque

	# Local performance tweak (Jake)
	lang="$LANG"
	LANG=C

	for tab in $(set | grep "^TAB.*=" | sort -V | sed 's/=.*//'); do
		eval applets=\$$tab
		case $applets in "") continue ;; esac

		numname=${tab#TAB} # <integer>'_'<uppercase English name>
		tabnum=${numname%%_*} tabEN=${numname#*_}

		# assign opt_START_TAB=<integer> on matching <uppercase English name>
		[ ${numname%_$opt_START_TAB} != $numname ] && opt_START_TAB=$tabnum

		# skip all tabs but that --one-tab, if defined
		[ "$opt_ONE_TAB" -a "$opt_ONE_TAB" != $tabnum -a "$opt_ONE_TAB" != $tabEN ] && continue

		tabname=${applets%%|*}
		applets=${applets#*|}

		# create liststore and new notebook tab
		define liststore gtk gtk_list_store_new 4 G_TYPE_STRING $GDK_TYPE_PIXBUF G_TYPE_STRING G_TYPE_STRING
		define iconview gtk gtk_icon_view_new_with_model $liststore
		gtk gtk_icon_view_set_text_column $iconview $T_TITLE
		gtk gtk_icon_view_set_pixbuf_column $iconview $T_PIXBUF
		gtk gtk_icon_view_set_tooltip_column $iconview $T_TOOLTIP
		gtk gtk_icon_view_set_item_width $iconview $ICON_WIDTH
		gtk gtk_server_connect $iconview item-activated activated-$iconview-$liststore

		define scrollwindow gtk gtk_scrolled_window_new
		gtk gtk_scrolled_window_set_policy $scrollwindow GTK_POLICY_AUTOMATIC GTK_POLICY_ALWAYS
		gtk gtk_scrolled_window_set_shadow_type $scrollwindow GTK_SHADOW_IN
		gtk gtk_container_add $scrollwindow $iconview

		define label gtk gtk_label_new \"$tabname\"
		gtk gtk_notebook_append_page $NOTEBOOK $scrollwindow $label
		gtk gtk_notebook_set_tab_pos $NOTEBOOK GTK_POS_LEFT

		# add applets to the new tab
		for a in $applets; do
			for b in $DESKTOP_FILES_DIRS; do
				if [ -e $b/${a}.desktop ]; then
					gtk gtk_list_store_append $liststore $iter
					title= comment= cmd= icon=NULL
					title1= title2= comment1= comment2=  # used for ex: de_BE and de
					while read p; do
						case $p in
							Name=*) title="${p#*=}" ;;
							#translation of Name for ex: 'de_BE' and/or 'de' added L18L
							Name\[${lang%%.*}\]=*)    title2="${p#*=}" ;;
							Name\[${lang%%_*}\]=*)    title1="${p#*=}" ;;

							Comment=*) comment="${p#*=}" ;;
							#translation of Comment for ex: 'de_BE' and/or 'de' added L18L
							Comment\[${lang%%.*}\]=*) comment2="${p#*=}" ;;
							Comment\[${lang%%_*}\]=*) comment1="${p#*=}" ;;

							Exec=*) cmd="${p#*=}" ;;
							Icon=*) find_icon "${p#*=}"  ;;
						esac
					done < $b/${a}.desktop

					[ "$title1" ] && title="$title1" #ex: Name[de] overwrites copy of Name
					[ "$title2" ] && title="$title2" #ex: Name[de_BE] overwrites copy of Name[de]

					[ "$comment1" ] && comment="$comment1" #ex: Comment[de] overwrites copy of Comment
					[ "$comment2" ] && comment="$comment2"

					gtk gtk_list_store_set $liststore $iter $T_TITLE \"$title\" $T_PIXBUF $icon $T_TOOLTIP \"$comment\" $T_EXEC \"$cmd\" -1
					APPLETS_COUNT=$(($APPLETS_COUNT + 1))
				fi
			done
		done
	done

	LANG="$lang"    # restore original lang

	gtk g_free $iter
	kill $xpid
	return 0
}
### helper - find the icon from all possible permutation of icon paths and icon extensions
# $1-icon name, returns "icon" widget (or NULL)
find_icon() {
	local icon_file path

	# special case
	if [ -e "$1" ]; then
		define icon gtk gdk_pixbuf_new_from_file_at_size \"$1\" $ICON_DISPLAY_SIZE NULL
		return
	fi

	# quick search
	for path in $ICON_DIRS; do
		for icon_file in "$path/${1}"*; do
			if [ -e "$icon_file" ]; then
				define icon gtk gdk_pixbuf_new_from_file_at_size \"$icon_file\" $ICON_DISPLAY_SIZE NULL
				return
			fi
		done
	done

	# full search
	icon_file=$(find $ICON_DIRS -name "${1}*" | head -n 1)
	[ "$icon_file" ] && define icon gtk gdk_pixbuf_new_from_file_at_size \"$icon_file\" $ICON_DISPLAY_SIZE NULL
	return 0
}

### signal handler - launch apps
#$1-iconview/liststore widget id
launch_applet() {
	local iconview=${1%-*} liststore=${1#*-} iter treepath cmd

	define iter gtk gtk_server_opaque
	define treepath gtk gtk_icon_view_get_cursor $iconview NULL

	gtk gtk_tree_model_get_iter $liststore $iter $treepath
	tree_model_get_string $liststore $iter $T_EXEC cmd
	cmd=$(echo "$cmd" | sed 's/%.//g') # remove %F %U etc
	$cmd &

	gtk gtk_tree_path_free $treepath
	gtk g_free $iter
}

################### main ###################
### Parse options
# Option format: -x[=parm] | --opt-x[=parm]
# Short options can't be combined together. Space can't substitute '=' before option value.
unset opt_START_TAB opt_ONE_TAB
while ! [ "${1#-}" = "$1" ]; do
	case "$1" in
		--start-tab=* ) # as either 1-based index or uppercase English name
			opt_START_TAB=${1#*=} ;;
		--one-tab=* ) # same as --start-tab but show no other
			opt_ONE_TAB=${1#*=}; opt_START_TAB=$opt_ONE_TAB ;;
		-h|--help|-h=*|--help=*) : usage "$1"; exit ;;
		--) shift; break ;;
		-*)
			echo >&2 "$(gettext "unknown option:")" "$1"
	esac
	shift
done

### init parameters - this app can be called with parameters and masquerade as launcher too!
[ "$1" ] && APPTITLE="$1" && APPICON=$APPICON2 && shift

### adjust gui for large screens
DPI=$(sed '/Xft.dpi/!d; s/.*:[ \t]*//' ~/.Xresources)
[ "$DPI" ] && [ $DPI -ge 132 ] && WINDOW_SIZE="$HIDPI_WINDOW_SIZE" # double the size

### init gui
add_missing_gtk_api
gtk gtk_init
define GDK_TYPE_PIXBUF gtk gdk_pixbuf_get_type

define MAIN_WINDOW gtk gtk_window_new GTK_WINDOW_TOPLEVEL
gtk gtk_window_set_title $MAIN_WINDOW \"$APPTITLE\"
gtk gtk_window_set_position $MAIN_WINDOW GTK_WIN_POS_CENTER
gtk gtk_container_set_border_width $MAIN_WINDOW 10
gtk gtk_window_set_default_size $MAIN_WINDOW ${WINDOW_SIZE% *} ${WINDOW_SIZE#* }
gtk gtk_window_set_icon_from_file $MAIN_WINDOW $APPICON

define NOTEBOOK gtk gtk_notebook_new
gtk gtk_container_add $MAIN_WINDOW $NOTEBOOK

# connect signals
gtk gtk_server_connect $MAIN_WINDOW delete-event quit

# find applets to load - either from command line or system applet
if [ "$1" ]; then
	#for a in $(seq 1 100); do eval unset TAB$a; done # clear any default applets
	while [ "$1" ]; do
		[ -e "$1" ] && . "$1"
		shift
	done
else
	[ -e $SYSTEM_APPLETS ] && . $SYSTEM_APPLETS
	[ -e $SYSTEM_APPLETS_DIR ] &&
	for control_file in $SYSTEM_APPLETS_DIR/*; do [ -r $control_file ] && . $control_file; done

	[ -e $USER_APPLETS ] && . $USER_APPLETS
	[ -e $USER_APPLETS_DIR ] &&
	for control_file in $USER_APPLETS_DIR/*; do [ -r $control_file ] && . $control_file; done
fi

# load all applets
load_applets

### gtk main loop
if [ $APPLETS_COUNT -ne 0 ]; then
	gtk gtk_widget_show_all $MAIN_WINDOW
	gtk gtk_notebook_set_current_page $NOTEBOOK $((${opt_START_TAB:-1} -1))
	while true; do
		define EVENT gtk gtk_server_callback wait
		case $EVENT in
			quit) break ;;
			activated*) launch_applet ${EVENT#*-} ;;
		esac
	done
else
	Xdialog --title "$(gettext 'Abort!')" --infobox "$(gettext 'No applet definitions found.')" 0 0 10000
fi
gtk gtk_server_exit



