#!/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
#
#140110 internationalisation by L18L, make use of translated Name and Comment in *.desktop files

# 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="500 300"		# in pixels, startup window width x height

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_WIDGET 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
# $1-tree $2-iter $3-index $4-result name
# static variable to speed up stuff
STATIC_BUF=
STATIC_ADDRESS=
tree_model_get_string() {
	[ -z "$STATIC_BUF" ] && define STATIC_BUF gtk malloc 32 # gtk-server MAX_DIG is 32 bytes
	
	gtk gtk_server_define gtk_tree_model_get NONE NONE 5 WIDGET WIDGET INT POINTER INT # ensure correct definition
	gtk gtk_tree_model_get $1 $2 $3 $STATIC_BUF -1
	
	gtk memcpy NULL $STATIC_BUF 32	# print address stored in $BUF --> *buf
	STATIC_ADDRESS=$GTK
	if [ $STATIC_ADDRESS -ne 0 ]; then
		gtk strcat $STATIC_ADDRESS \"\" # get string point by address --> **buf	
		eval $4=\'"$GTK"\'				# this will be our return value	
		gtk free $STATIC_ADDRESS		# this was dynamically alloc-ed by gtk_tree_model_get, now free it
	else
		eval $4=\"\"
	fi
}


load_applets() {
	local iter tab applets tabname liststore iconview scrollwindow label title comment cmd icon xpid
	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
	for tab in $(set | grep "^TAB.*=" | sort -V | sed 's/=.*//'); do
		eval applets=\$$tab
		case $applets in "") continue ;; esac
		
		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_AUTOMATIC
		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
		
		# add applets to the new
		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
	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 ###################
### init parameters - this app can be called with parameters and masquerade as launcher too!
[ "$1" ] && APPTITLE="$1" && APPICON=$APPICON2 && shift

### 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
	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 



