#!/bin/sh

[ -z $GTKDIALOG ] && GTKDIALOG=gtkdialog

# Pfeme (c) Thunor 2012
# GNU GENERAL PUBLIC LICENSE Version 2, June 1991
# See http://www.gnu.org/licenses/gpl-2.0.txt

## Strip comments from the entire project using:
## for f in func* main; do sed -i -e '/## /d' -e '/ ##/d' $f; done
## 
## Exiting and Restarting
## ----------------------
## 
## It's possible to exit via the menu or the button but the button will
## activate the menuitem because it's faster -- presumably because it
## doesn't require animating like a button -- which is useful to know.
## 
##  +---------+       +-------------------------------+
##  | btnQuit |       |        btnReadmeApply         |
##  +---------+       +-------------------------------+
##       |                            |
##       |                            v Launches
##       |                            |
##       |            +-------------------------------+
##       v            | winReadmeApplyRestartRequired |
##       |            +-------------------------------+
##       |                            |
##       |                            v Activates
##       | Activates                  |
##       |            +-------------------------------+            +--------------------------+
##       +----->------|            muiQuit            |------>-----| winProfileNotSavedOnQuit |
##                    +-------------------------------+  Launches  +--------------------------+
##                                    |                                         |
##                                    v Activates                               v
##                                    |                               Activates |
##                    +-------------------------------+                         |
##                    |            muiExit            |------------<------------+
##                    +-------------------------------+
## 

## ----------------------------------------------------------------------------
## Constants
## ----------------------------------------------------------------------------
## These are application defaults which may be updated from rcfiles.

TARGET=pfeme
TITLE=Pfeme
VERSION=0.1.1
LOCAL_DATA_DIR=$HOME/.$TARGET
PACKAGE_DATA_DIR=`pwd`
PROFILE_MAX=5
## Setting these to non-zero will generate lots of useful debugging info.
DEBUG_CONTENT=0
DEBUG_TRANSITS=0

## These don't require exporting.
DebugSpacer=
#"[]"
DebugTime=
#time
ImageSize=240
OutputForeground="#ffffff"
OutputBackground="#000000"
OutputFontName="Monospace 10"
ProfileSelected=0
ProfileVisible=0
ReadmeForeground="#000000"
ReadmeBackground="#ffffff"
ReadmeFontName="Monospace 10"
WindowWidth=600
WindowHeight=400

## ----------------------------------------------------------------------------
## Includes
## ----------------------------------------------------------------------------
## These files are sourced from the PATH.

PATH=$PACKAGE_DATA_DIR:$PATH

. funcDialogsCreate
. funcGameListCreate
. funcgtkrcCreate
. funcImagesUpdate
. funcProfileImagesUpdate
. funcProfileLoad
. funcReadmeUpdate
. funcrcCreate
. funcrcGet
. funcrcSet
. functmpGet
. functmpSet

## ----------------------------------------------------------------------------
## Local Functions
## ----------------------------------------------------------------------------

## Enable the embedding of comments within the XML.
Comment() { :; }

## Get major/minor/micro version of an application.
## On entry: $1 = command
##           $2 = index into string to locate version
##  On exit: Initialises major/minor/micro_version
funcAppVersionGet() {
	local field
	local index=0

	for field in `$1`; do
		if [ $index -eq $2 ]; then break; fi; index=$((index + 1))
	done
	major_version=${field%%.*}; field=${field#*.}
	minor_version=${field%%.*}
	micro_version=${field#*.}
}

## Create the XML for the Games tab.
funcTabGamesCreate() {
	echo '<vbox border-width="5">
			<tree column-type="string|string|string|uint64" has-focus="true"
				column-visible="false" rules-hint="true" selection-mode="2"
				auto-sort="true" sort-column="1">
				<variable>treGameList</variable>
				<label>Path|Name|Type|x Played</label>
				<input file>'$LOCAL_DATA_DIR'/GameList</input>
				<action>activate:tgbGamePlay</action>
				<action signal="cursor-changed">enable:tgbGamePlay</action>
				<action signal="cursor-changed">enable:entProfileCommandLineArgs</action>
				<action signal="cursor-changed">. funcImagesUpdate; funcImagesUpdate</action>
				<action signal="cursor-changed">refresh:chkImagesRefreshCheck</action>
		</tree>
			<hbox homogeneous="true" space-expand="false" space-fill="true">
				<text><label>"'$DebugSpacer'"</label></text>
'`Comment ##
## Using a togglebutton here instead of a button allows gtk to repaint
## the window in-between clearing and disabling and refreshing which is
## preferential to having everything freeze and then update all at once.
## `'
				<togglebutton use-stock="true" label="gtk-refresh">
					<variable export="false">tgbGameListRefresh</variable>
					<action>if true clear:treGameList</action>
					<action>if true clear:stbGameCount</action>
					<action>if true disable:tgbGamePlay</action>
					<action>if true disable:entProfileCommandLineArgs</action>
					<action>if true . funcImagesUpdate; funcImagesUpdate 2</action>
					<action>if true activate:tgbGameListRefresh</action>
					<action>if false . funcGameListCreate; '$DebugTime' funcGameListCreate</action>
					<action>if false refresh:treGameList</action>
					<action>if false refresh:stbGameCount</action>
					<action>if false grabfocus:treGameList</action>
					<action>if false refresh:chkImagesRefreshCheck</action>
				</togglebutton>
'`Comment ##
## Getting the window to hide before executing the emulator requires that
## gtk returns to its main loop, so its a two stage process made possible
## by the use of the activate function. Initially I had the Play button
## hide the window before activating a second button which executed the
## emulator but I managed to combine the two into a tidier togglebutton.
## `'
				<togglebutton use-underline="true">
					<variable export="false">tgbGamePlay</variable>
					<label>_Play</label>
					<sensitive>false</sensitive>
					<input file stock="gtk-execute"></input>
					<action>if true hide:winMain</action>
					<action>if true activate:tgbGamePlay</action>
					<action>if false . funcProfileSave; funcProfileSave 1; . funcxPlayedUpdate; funcxPlayedUpdate; . funcGamePlay; funcGamePlay; . funcImagesUpdate; funcImagesUpdate 1</action>
					<action>if false refresh:chkImagesRefreshCheck</action>
					<action>if false clear:ediOutput</action>
					<action>if false refresh:ediOutput</action>
					<action>if false show:winMain</action>
				</togglebutton>
				<text><label>"'$DebugSpacer'"</label></text>
			</hbox>
		</vbox>'
}

## Create the XML for the Profiles tab.
funcTabProfilesCreate() {
	local f tab

	echo '<vbox border-width="5">
			<hbox>'
	f=0; while [ $f -lt $PROFILE_MAX ]; do
		echo '<button>
				<variable export="false">btnProfile'$f'</variable>
				<width>48</width><height>48</height>
				<input file>'$TEMP_DIR'/Profile'$f'</input>
				<action>. functmpSet; functmpSet ProfileTarget '$f'</action>
				<action condition="sensitive_is_true(btnProfileApply)">launch:winProfileNotSavedOnChange</action>
'`Comment ##
## If btnProfileApply was sensitive and winProfileNotSavedOnChange was
## launched then we won't reach this point because launching doesn't
## return to process the remaining actions so we can safely assume that
## btnProfileApply is not sensitive which makes a condition redundant.
## `'
				<action>activate:muiProfileChange</action>
			</button>'
		f=$((f + 1))
	done
	echo '</hbox>
			<frame>'
	for f in Executable Arguments Readme; do
		echo '<hbox>
				<text space-expand="false" space-fill="false"
					width-request="70">
					<label>"'$f'"</label>
				</text>
				<entry fs-action="file" fs-folder="'$HOME'" fs-title="'$TITLE'"
					space-expand="true" space-fill="true"
					width-request="314">
					<variable>entProfile'$f'</variable>
					<input file>'$TEMP_DIR'/Profile'$f'</input>
					<action signal="changed">enable:btnProfileApply</action>
					<action signal="changed">disable:btnProfileSelect</action>
				</entry>
				<vbox>'
		## Arguments doesn't require a fileselect button so we'll use a
		## label with a fixed size. Nothing is going to be perfect here
		## since the theme controls button and image properties but it's
		## correct most of the time with the themes that I've tested.
		## The button has to go inside a vbox to isolate it otherwise it
		## stretches vertically which is not what we want here.
		if [ $f = "Arguments" ]; then
			echo '<text space-expand="true" space-fill="false"
					width-request="30">
					<label>"'$DebugSpacer'"</label>
				</text>'
		else
			echo '<button space-expand="true" space-fill="false">
					<input file stock="gtk-new"></input>
					<action>fileselect:entProfile'$f'</action>
				</button>'
		fi
		echo '</vbox>'
		## If the button was up against the edge it would stretch
		## horizontally which an invisible label luckily prevents.
		echo '<text space-expand="false" space-fill="false"
				width-request="0" visible="false">
				<label>"'$DebugSpacer'"</label>
			</text>'
		echo '</hbox>'
	done
	echo '</frame>
			<notebook tab-labels="ROMs|Images">
				<vbox border-width="5">'
	for f in 0 1 2 3 4 5; do
		if [ $f -lt 3 ]; then
			tab=ROMs
		else
			tab=Images
		fi
		echo '<hbox>
				<text space-expand="false" space-fill="false">
					<label>Path</label>
				</text>
				<entry fs-action="folder" fs-folder="'$HOME'" fs-title="'$TITLE'"
					space-expand="true" space-fill="true"
					width-request="220">
					<variable>entProfile'$tab'Path'$((f % 3))'</variable>
					<input file>'$TEMP_DIR'/Profile'$tab'Path'$((f % 3))'</input>
					<action signal="changed">enable:btnProfileApply</action>
					<action signal="changed">disable:btnProfileSelect</action>
				</entry>
				<vbox>
					<button space-expand="true" space-fill="false">
						<input file stock="gtk-open"></input>
						<action>fileselect:entProfile'$tab'Path'$((f % 3))'</action>
					</button>
				</vbox>
				<text space-expand="false" space-fill="false">
					<label>Pattern</label>
				</text>
				<entry space-expand="true" space-fill="true"
					width-request="80">
					<variable>entProfile'$tab'Pattern'$((f % 3))'</variable>
					<input file>'$TEMP_DIR'/Profile'$tab'Pattern'$((f % 3))'</input>
					<action signal="changed">enable:btnProfileApply</action>
					<action signal="changed">disable:btnProfileSelect</action>
				</entry>
			</hbox>'
		if [ $f -eq 2 ]; then
			echo '</vbox>
				<vbox border-width="5">'
		fi
	done
	echo '</vbox>
			</notebook>
			<hbox homogeneous="true" space-expand="false" space-fill="true">
				<text><label>"'$DebugSpacer'"</label></text>
				<button use-stock="true" label="gtk-apply">
					<variable export="false">btnProfileApply</variable>
					<sensitive>false</sensitive>
					<action>. funcProfileSave; funcProfileSave</action>
					<action>disable:btnProfileApply</action>
					<action>enable:btnProfileSelect</action>
					<action condition="command_is_true([ `. funcrcGet; funcrcGet ProfileSelected` -ne `. functmpGet; functmpGet ProfileVisible` ] && echo true)" function="break">""</action>
					<action>. funcWidgetSchedule; funcWidgetSchedule chkContentRefreshCheck ContentRefreshCheck</action>
					<action>refresh:chkContentRefreshCheck</action>
					<action>disable:btnProfileSelect</action>
				</button>
				<button use-underline="true">
					<variable export="false">btnProfileSelect</variable>
					<label>_Select</label>
					<sensitive>false</sensitive>
					<input file stock="gtk-ok"></input>
					<action>. funcProfileSave; funcProfileSave 1; . funcProfileSelect; funcProfileSelect</action>
					<action>refresh:entProfileCommandLineArgs</action>
					<action>refresh:chkContentRefreshCheck</action>
					<action>disable:btnProfileSelect</action>
					<action>activate:muiProfileButtonsUpdate</action>
				</button>
				<text><label>"'$DebugSpacer'"</label></text>
			</hbox>
		</vbox>'
}

## Create the XML for the Readme tab.
funcTabReadmeCreate() {
	echo '<vbox border-width="5">
			<edit name="TabReadme" editable="false" cursor-visible="false">
				<variable export="false">ediReadme</variable>
				<input file>'$TEMP_DIR'/Readme</input>
			</edit>
			<expander use-underline="true" space-expand="false" space-fill="true">
				<hbox>
					<colorbutton tooltip-text="Foreground">
						<variable>clbReadmeForeground</variable>
						<default>'$ReadmeForeground'</default>
						<action>enable:btnReadmeApply</action>
					</colorbutton>
					<colorbutton tooltip-text="Background">
						<variable>clbReadmeBackground</variable>
						<default>'$ReadmeBackground'</default>
						<action>enable:btnReadmeApply</action>
					</colorbutton>
					<fontbutton tooltip-text="Font Name">
						<variable>ftbReadmeFontName</variable>
						<default>'$ReadmeFontName'</default>
						<action>enable:btnReadmeApply</action>
					</fontbutton>
					<button use-stock="true" label="gtk-apply">
						<variable export="false">btnReadmeApply</variable>
						<sensitive>false</sensitive>
						<action>. funcrcSet; funcrcSet ReadmeForeground "$clbReadmeForeground"; funcrcSet ReadmeBackground "$clbReadmeBackground"; funcrcSet ReadmeFontName "$ftbReadmeFontName"</action>
						<action>launch:winReadmeApplyRestartRequired</action>
					</button>
				</hbox>
				<variable>expReadmeOptions</variable>
				<label>_Options</label>
				<input file>'$TEMP_DIR'/ReadmeOptions</input>
			</expander>
		</vbox>'
}

## Create the XML for the Output tab.
funcTabOutputCreate() {
	echo '<vbox border-width="5">
			<edit name="TabOutput" editable="false" cursor-visible="false">
				<variable export="false">ediOutput</variable>
				<input file>'$TEMP_DIR'/Output</input>
			</edit>
		</vbox>'
}

## Create the XML for the Images tabs.
funcTabsImagesCreate() {
	for f in 0 1 2; do
		echo '<eventbox name="TabsImages" border-width="5">
				<pixmap>
					<variable export="false">pmpImage'$f'</variable>
					<width>'$ImageSize'</width>
					<height>'$ImageSize'</height>
					<input file>'$TEMP_DIR'/Image'$f'</input>
				</pixmap>
			</eventbox>'
	done
}

## ----------------------------------------------------------------------------
## Main
## ----------------------------------------------------------------------------

## Check requirements.
if [ ! `command -v $GTKDIALOG` ]; then
	echo "Couldn't find $GTKDIALOG"
	exit 1
fi
funcAppVersionGet "$GTKDIALOG -v" 2
if [ $minor_version -ge 8 -a $micro_version -ge 3 ]; then
	true
else
	echo "Couldn't find $GTKDIALOG >= 0.8.3"
	exit 1
fi

## Create the local data directory if it doesn't already exist.
if [ ! -d $LOCAL_DATA_DIR ]; then
	mkdir $LOCAL_DATA_DIR
	if [ $? -ne 0 ]; then
		echo "Couldn't create $LOCAL_DATA_DIR"
		exit 1
	fi
fi

## Create the rcfiles.
funcrcCreate

## Process the command-line argument(s).
if [ "$1" = -h -o "$1" = --help ]; then
	echo "$TITLE $VERSION (C) 2012 Thunor
Usage: $TARGET

See $PACKAGE_DATA_DIR/README for more information.
"
	exit 1
fi

## Create a temporary directory.
TEMP_DIR=`mktemp -d -t ${TARGET}.XXXXXXXX`
if [ $? -ne 0 ]; then
	echo "Couldn't create temporary directory."
	exit 1
fi

## Application restart loop if required.
while [ true ]; do

	## Load the initial rcfiles required.
	ReadmeForeground="`funcrcGet ReadmeForeground`"
	ReadmeBackground="`funcrcGet ReadmeBackground`"
	ReadmeFontName="`funcrcGet ReadmeFontName`"
	ProfileSelected="`funcrcGet ProfileSelected`"
	WindowWidth="`funcrcGet WindowWidth`"
	WindowHeight="`funcrcGet WindowHeight`"

	## Create a custom style.
	funcgtkrcCreate

	## Create the initial files required.
	functmpSet Output ""
	functmpSet ContentRefreshCheck false
	functmpSet ImagesRefreshCheck false
	funcProfileLoad $ProfileSelected 1
	funcProfileImagesUpdate
	funcGameListCreate 1
	funcReadmeUpdate
	funcImagesUpdate 2
	## These are retained on an application restart.
	if [ ! -f $TEMP_DIR/ApplicationRestart ]; then
		functmpSet ApplicationRestart false
		functmpSet NotebookMainPage 0
		functmpSet NotebookImagesPage 0
		functmpSet ReadmeOptions false
	fi

	## Create the XML for the pop-up dialogs.
	funcDialogsCreate

	## Create the XML for the main dialog.
	echo '
<window title="'$TITLE'" icon-name="'$TARGET'" window-position="1"
	default-width="'$WindowWidth'" default-height="'$WindowHeight'" border-width="0">
	<vbox spacing="0">
		<menubar space-expand="false" space-fill="false">
			<menu use-underline="true" label="_File">
				<menuitem stock-id="gtk-quit" accel-key="0x51" accel-mods="4">
					<variable export="false">muiQuit</variable>
					<action condition="sensitive_is_true(btnProfileApply)">launch:winProfileNotSavedOnQuit</action>
'`Comment ##
## We won't reach this point if winProfileNotSavedOnQuit was launched.
## `'
					<action>activate:muiExit</action>
				</menuitem>
			</menu>
			<menu use-underline="true" label="_Help">
				<menuitem stock-id="gtk-help" accel-key="0xffbe" accel-mods="0">
					<action>launch:winHelp</action>
				</menuitem>
				<menuitem use-underline="true" label="_Website">
					<action>. funcURLOpen; funcURLOpen "http://www.murga-linux.com/puppy/viewtopic.php?t=83564"</action>
				</menuitem>
				<menuitemseparator></menuitemseparator>
				<menuitem stock-id="gtk-about">
					<action>launch:winAbout</action>
				</menuitem>
			</menu>
			<menu visible="false">
'`Comment ##
## This menuitem -- activated by all of the buttons that change the
## visible profile -- is loading a profile and refreshing the widgets.
## This enables a list of actions to be executed as a function.
## `'
				<menuitem>
					<variable export="false">muiProfileChange</variable>
					<action>. funcProfileLoad; funcProfileLoad `. functmpGet; functmpGet ProfileTarget`</action>
					<action>refresh:entProfileExecutable</action>
					<action>refresh:entProfileArguments</action>
					<action>refresh:entProfileReadme</action>
					<action>refresh:entProfileROMsPath0</action>
					<action>refresh:entProfileROMsPattern0</action>
					<action>refresh:entProfileROMsPath1</action>
					<action>refresh:entProfileROMsPattern1</action>
					<action>refresh:entProfileROMsPath2</action>
					<action>refresh:entProfileROMsPattern2</action>
					<action>refresh:entProfileImagesPath0</action>
					<action>refresh:entProfileImagesPattern0</action>
					<action>refresh:entProfileImagesPath1</action>
					<action>refresh:entProfileImagesPattern1</action>
					<action>refresh:entProfileImagesPath2</action>
					<action>refresh:entProfileImagesPattern2</action>
					<action>disable:btnProfileApply</action>
					<action>disable:btnProfileSelect</action>
					<action condition="command_is_true([ `. funcrcGet; funcrcGet ProfileSelected` -ne `. functmpGet; functmpGet ProfileVisible` ] && echo true)">enable:btnProfileSelect</action>
					<action>activate:muiProfileButtonsUpdate</action>
				</menuitem>
'`Comment ##
## This enables a list of actions to be executed as a function.
## `'
				<menuitem>
					<variable export="false">muiProfileButtonsUpdate</variable>
					<action>. funcProfileImagesUpdate; funcProfileImagesUpdate</action>
					<action>refresh:btnProfile0</action>
					<action>refresh:btnProfile1</action>
					<action>refresh:btnProfile2</action>
					<action>refresh:btnProfile3</action>
					<action>refresh:btnProfile4</action>
				</menuitem>
'`Comment ##
## The application is exited via this menuitem so that anything that
## needs to be saved can be managed here and only here.
## `'
				<menuitem>
					<variable export="false">muiExit</variable>
					<action>. funcProfileSave; funcProfileSave 1; . funcWindowDimSave; funcWindowDimSave "'$TITLE'"; . functmpSet; functmpSet ReadmeOptions $expReadmeOptions; functmpSet NotebookMainPage $nbkMain; functmpSet NotebookImagesPage $nbkImages</action>
					<action>exit:Quit</action>
				</menuitem>
			</menu>
		</menubar>
		<vbox border-width="5" space-expand="true" space-fill="true">
			<hbox>
				<vbox width-request="">
					<notebook tab-labels="Games|Profiles|Readme|Output">
						'"`funcTabGamesCreate`"'
						'"`funcTabProfilesCreate`"'
						'"`funcTabReadmeCreate`"'
						'"`funcTabOutputCreate`"'
						<variable>nbkMain</variable>
						<input file>'$TEMP_DIR'/NotebookMainPage</input>
					</notebook>
				</vbox>
				<vbox>
					<notebook tab-prefix="Image ">
						'"`funcTabsImagesCreate`"'
						<variable>nbkImages</variable>
						<input file>'$TEMP_DIR'/NotebookImagesPage</input>
					</notebook>
					<text height-request=""><label>"'$DebugSpacer'"</label></text>
				</vbox>
			</hbox>
			<hbox space-expand="false" space-fill="true">
				<text space-expand="false" space-fill="true">
					<label>Command Line Args.</label>
				</text>
				<entry secondary-icon-stock="gtk-clear"
					space-expand="true" space-fill="true">
					<variable>entProfileCommandLineArgs</variable>
					<sensitive>false</sensitive>
					<input file>'$TEMP_DIR'/ProfileCommandLineArgs</input>
					<action signal="activate">activate:tgbGamePlay</action>
					<action signal="secondary-icon-release">clear:entProfileCommandLineArgs</action>
				</entry>
				<button use-stock="true" label="gtk-quit"
					space-expand="false" space-fill="false">
					<action>activate:muiQuit</action>
				</button>
			</hbox>
		</vbox>
		<statusbar space-expand="false" space-fill="false">
			<variable export="false">stbGameCount</variable>
			<input file>'$TEMP_DIR'/GameCount</input>
		</statusbar>
'`Comment ##
## This enables the conditional refreshing of the main content.
## `'
		<checkbox visible="false">
			<variable>chkContentRefreshCheck</variable>
			<input file>'$TEMP_DIR'/ContentRefreshCheck</input>
			<action>. funcReadmeUpdate; funcReadmeUpdate</action>
			<action>refresh:ediReadme</action>
			<action>activate:tgbGameListRefresh</action>
		</checkbox>
'`Comment ##
## This enables the conditional refreshing of the game images.
## `'
		<checkbox visible="false">
			<variable>chkImagesRefreshCheck</variable>
			<input file>'$TEMP_DIR'/ImagesRefreshCheck</input>
			<action>refresh:pmpImage0</action>
			<action>refresh:pmpImage1</action>
			<action>refresh:pmpImage2</action>
		</checkbox>
	</vbox>
	<variable export="false">winMain</variable>
'`Comment ##
## Notebook pages are set from here because the initial input from file
## doesn't have an effect as the notebook widget isn't realized then.
## The page property isn't suitable because I need the widgets on page0
## to realize first otherwise several widgets don't get initialised.
## `'
	<action signal="show" condition="command_is_true([ `. functmpGet; functmpGet ApplicationRestart` = false ] && echo true)" function="break">""</action>
	<action signal="show">. functmpSet; functmpSet ApplicationRestart false</action>
	<action signal="show">refresh:nbkMain</action>
	<action signal="show">refresh:nbkImages</action>
</window>
' > $TEMP_DIR/winMain

	## Export everything necessary.
	export GTKDIALOG
	export TARGET
	export TITLE
	export VERSION
	export LOCAL_DATA_DIR
	export PACKAGE_DATA_DIR
	export PROFILE_MAX
	export DEBUG_CONTENT
	export DEBUG_TRANSITS
	export PATH
	export TEMP_DIR
	export GTK2_RC_FILES
	export winProfileNotSavedOnChange
	export winProfileNotSavedOnQuit
	export winReadmeApplyRestartRequired
	export winAbout
	export winHelp

	## And off we go...
	$GTKDIALOG --space-expand=true --space-fill=true --file=$TEMP_DIR/winMain

	if [ `functmpGet ApplicationRestart` = false ]; then break; fi

done

## Remove the temporary files.
rm -rf $TEMP_DIR
