#!/bin/bash

###############################################################################
# JWMKeyConfig v0.8 by SFR'2014                                               #
# GNU GPL v2 applies                                                          #
# radky 151020: adjust for JWMDesk
###############################################################################

export TEXTDOMAIN=puppy
export OUTPUT_CHARSET=UTF-8

#------------------------------------------------------------------------------

TMPDIR=/tmp/jwmkeyconfig
mkdir -p $TMPDIR

export CONFIG_FILE="$HOME/.jwm/jwmrc-personal"
export CONFIG_FILE_BAK="$HOME/.jwm/jwmrc-personal2"
export TMP_CUTS="$TMPDIR/jwm_keycuts"
export TMP_CONFIG_FILE="$TMPDIR/jwmrc-personal_temp"

trap 'rm -rf $TMPDIR' EXIT

export FAKE="`echo -e '\x00\xe2\x88\xa3'`"   # Divides (U+2223)

#[ -e /usr/share/pixmaps/puppy/keyboard.svg ] && PIC_PATH=/usr/share/pixmaps/puppy/keyboard.svg || PIC_PATH=/usr/local/lib/X11/mini-icons/mini-keyboard.xpm
PIC_PATH=/usr/local/jwmdesk/icons/keyboard_shortcut.svg

#------------------------------------------------------------------------------

func_msg () {
  BUT=''
  case "$1" in
    CONFIRM)	TITLE="$(gettext 'Confirmation')";	BG=yellow;		BUT='-buttons Yes:100,No:101'; shift ;;
    WARNING)	TITLE="$(gettext 'Warning')";		BG=orange;		shift ;;
    ERROR)		TITLE="$(gettext 'Error')";			BG=red;			shift ;;
    INFO)		TITLE="$(gettext 'Info')";			BG=lightblue;	shift ;;
    *)			TITLE="$(gettext 'Info')";			BG=lightblue ;;
  esac
  xmessage $BUT -bg $BG -center -title "$TITLE" "$@"
}
export -f func_msg

#------------------------------------------------------------------------------

func_modmap_list () {
  xmodmap -pk | grep -o "(.*)" | cut -f1 | grep -v "NoSymbol" | tail -n +3 | tr -d "()" | awk '!x[$0]++' 
}
export -f func_modmap_list

#------------------------------------------------------------------------------

func_get_keys() {
  echo -n > $TMP_CUTS
  CNT=1
  OIFS="$IFS"; IFS=$'\n'
  for i in `grep '^<Key .*>' "$CONFIG_FILE"`; do
    MASK="`echo $i | grep -o ' mask=".*' | cut -f2 -d '"'`"
    KEY="`echo $i | grep -o ' key=".*">' | cut -f2- -d '"'`"
    KEY="${KEY%??}"
    KEYCODE="`echo $i | grep -o ' keycode=".*' | cut -f2 -d '"'`"
    ACTION="`echo $i | rev | cut -f2- -d '<' | rev | cut -f2- -d '>'`"
    # if 'action' contains | we need to convert it or else <tree> would cut the line
    ACTION="${ACTION//|/$FAKE}"

    [ "$KEY" = "" ] && KEY="$KEYCODE"
    
    if [ "$MASK" != "" ]; then
      MASK="`echo $MASK | sed -e 's/.\{1\}/& /g' -e "s/A/Alt/" -e "s/C/Ctrl/" -e "s/S/Shift/" -e "s/1/Mod1/" -e "s/2/Mod2/" -e "s/3/Mod3/" -e "s/4/Mod4/" -e "s/5/Mod5/" | tr ' ' '+'`"
      MASK="${MASK%?}"
    fi
    
    echo "${CNT}|${MASK}|${KEY}|${ACTION}|${i}" >> $TMP_CUTS
    ((CNT++))
  done
  IFS="$OIFS"
}
export -f func_get_keys

#------------------------------------------------------------------------------

func_merge_line () {
  [ "`echo "$varNEW_KEY" | grep -E '\||>'`" != "" ] && { func_msg ERROR "$(gettext 'Those characters are not allowed within "key" field: | >')"; return 1; }
  LINE="<Key"
  
  MASK=""
  [ "$varNEW_A" = "true" ] && MASK="${MASK}A"
  [ "$varNEW_C" = "true" ] && MASK="${MASK}C"
  [ "$varNEW_S" = "true" ] && MASK="${MASK}S"
  [ "$varNEW_1" = "true" ] && MASK="${MASK}1"
  [ "$varNEW_2" = "true" ] && MASK="${MASK}2"
  [ "$varNEW_3" = "true" ] && MASK="${MASK}3"
  [ "$varNEW_4" = "true" ] && MASK="${MASK}4"
  [ "$varNEW_5" = "true" ] && MASK="${MASK}5"
  
  [ "$MASK" != "" ] && LINE="${LINE} mask=\"$MASK\""

  if [ "$varUSEKEY" = "true" ]; then
    varNEW_KEY="${varNEW_KEY// /}"		# no spaces allowed, trim if any
    [ "$varNEW_KEY" = "" ] && { func_msg ERROR "$(gettext '"Key" field can not be empty!')"; return 1; }
    LINE="${LINE} key=\"$varNEW_KEY\""
  else
    LINE="${LINE} keycode=\"$varNEW_KEYCODE\""
  fi
}
export -f func_merge_line

#------------------------------------------------------------------------------

func_precaution () {
  # if more than one line is about to be changed, then most likely something's really wrong
  if [ `grep -cf "$TMP_CONFIG_FILE" -vFx "$CONFIG_FILE"` -gt 1 ]; then
    func_msg ERROR "$(gettext 'Unexpected error!
The modification would affect more than one line in configuration file,
what should not take place.
Aborted, in order to prevent damages...')"
    return 1
  fi
}
export -f func_precaution

#------------------------------------------------------------------------------

func_add () {
  func_merge_line || return 1
  grep -q -F "${LINE}" "$CONFIG_FILE" && { func_msg ERROR "$(gettext 'Shortcut already exists!')"; return 1; }
  LINE="${LINE}>${varNEW_ACTION//$FAKE/|}</Key>"

  if [ "$varSHORTCUT" = "" ]; then
    LAST_LINE="<!-- Key bindings -->"
  else
    LAST_LINE="`grep . "$TMP_CUTS" | tail -n 1 | cut -f5- -d '|'`"
  fi
  
  LINE_NO="`grep -m1 -n -F "$LAST_LINE" "$CONFIG_FILE" | cut -f1 -d ':'`"
  head -n $LINE_NO "$CONFIG_FILE" > "$TMP_CONFIG_FILE"
  echo "$LINE" >> "$TMP_CONFIG_FILE"
  tail -n +$((LINE_NO+1)) "$CONFIG_FILE" >> "$TMP_CONFIG_FILE"
  
  func_precaution || return 1
  
  mv -f "$CONFIG_FILE" "$CONFIG_FILE_BAK"
  mv -f "$TMP_CONFIG_FILE" "$CONFIG_FILE"
  
  func_msg INFO "$(gettext 'Shortcut has been succesfully added.')"
}
export -f func_add

#------------------------------------------------------------------------------

func_edit () {
  OLD_LINE="`grep "^${varSHORTCUT}|" "$TMP_CUTS" | cut -f5- -d '|'`"
  
  func_merge_line || return 1
  LINE="${LINE}>${varNEW_ACTION//$FAKE/|}</Key>"
  
  # sed is useless when there are some special chars, let's use foolproof head/tail pair...
  LINE_NO="`grep -m1 -F -n "$OLD_LINE" "$CONFIG_FILE" | cut -f1 -d ':'`"
  head -n $((LINE_NO-1)) "$CONFIG_FILE" > "$TMP_CONFIG_FILE"
  echo "$LINE" >> "$TMP_CONFIG_FILE"
  tail -n +$((LINE_NO+1)) "$CONFIG_FILE" >> "$TMP_CONFIG_FILE"
  
  func_precaution || return 1
  
  mv -f "$CONFIG_FILE" "$CONFIG_FILE_BAK"
  mv -f "$TMP_CONFIG_FILE" "$CONFIG_FILE"
  
  func_msg INFO "$(gettext 'Shortcut has been succesfully modified.')"
}
export -f func_edit

#------------------------------------------------------------------------------

func_delete () {
  func_msg CONFIRM "$(gettext 'Are you sure you want to delete the selected shortcut?')"
  [ $? -ne 100 ] && return 1
  
  # last field contains original line from jwmrc-personal
  LINE="`sed -n "${varSHORTCUT}p" "$TMP_CUTS" | cut -f5- -d '|'`"
  grep -F -v "$LINE" "$CONFIG_FILE" > "$TMP_CONFIG_FILE"
  
  func_precaution || return 1
  
  mv -f "$CONFIG_FILE" "$CONFIG_FILE_BAK"
  mv -f "$TMP_CONFIG_FILE" "$CONFIG_FILE"
}
export -f func_delete

#==============================================================================

export GUI_ADD='
<window title="'$(gettext "Add new shortcut")'" modal="true" image-name="'${PIC_PATH}'">
  <vbox>

    <frame '$(gettext "Key/Keycode")'>
      <hbox>
        <radiobutton space-fill="false" space-expand="false">
          <variable>varUSEKEY</variable>
          <default>true</default>
          <label>'$(gettext "Key:")' </label>
          <action>if true enable:varNEW_KEY</action>
          <action>if false disable:varNEW_KEY</action>
        </radiobutton>
      
        <comboboxentry>
          <variable>varNEW_KEY</variable>
          <input>func_modmap_list</input>
        </comboboxentry>
        
        <text space-fill="true" space-expand="true"><label>" "</label></text>
        
        <radiobutton>
          <variable>varUSEKEYCODE</variable>
          <default>false</default>
          <label>'$(gettext "Keycode:")'</label>
          <action>if true enable:varNEW_KEYCODE</action>
          <action>if false disable:varNEW_KEYCODE</action>
        </radiobutton>
        <spinbutton range-min="0" range-max="255" range-step="1" range-value="0">
          <variable>varNEW_KEYCODE</variable>
          <sensitive>false</sensitive>
        </spinbutton>
      
      </hbox>
    </frame>
    
    <frame '$(gettext "Mask")'>
      <hbox homogeneous="true">
        <checkbox>
          <variable>varNEW_A</variable>
          <label>Alt</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_C</variable>
          <label>Ctrl</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_S</variable>
          <label>Shift</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_1</variable>
          <label>Mod1</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_2</variable>
          <label>Mod2</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_3</variable>
          <label>Mod3</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_4</variable>
          <label>Mod4</label>
        </checkbox>
        <checkbox>
          <variable>varNEW_5</variable>
          <label>Mod5</label>
        </checkbox>
      </hbox>
    </frame>
    
    <frame '$(gettext "Action")'>
      <hbox>
        <entry>
          <variable>varNEW_ACTION</variable>
        </entry>
      </hbox>
    </frame>

    <hbox>
      <button>
        <input file stock="gtk-apply"></input>
        <label>'$(gettext "Apply")'</label>
        <action>func_add</action>
        <action>func_get_keys</action>
        <action>refresh:varSHORTCUT</action>
        <action>grabfocus:varSHORTCUT</action>
      </button>
      <button>
        <input file stock="gtk-close"></input>
        <label>'$(gettext "Close")'</label>
        <action>grabfocus:varSHORTCUT</action>
        <action>closewindow:ADD_WINDOW</action>
      </button>
    </hbox>
    
    <variable>ADD_WINDOW</variable>
  </vbox>
</window>'

#------------------------------------------------------------------------------

export GUI_EDIT='
<window title="'$(gettext "Edit existing shortcut")'" modal="true" image-name="'${PIC_PATH}'">
  <vbox>

    <frame '$(gettext "Key/Keycode")'>
      <hbox>
        <radiobutton space-fill="false" space-expand="false">
          <variable>varUSEKEY</variable>
          <input>[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f5 -d "|" | grep "key="`" != "" ] && echo true || echo false</input>
          <label>'$(gettext "Key:")' </label>
          <action signal="show">if true disable:varNEW_KEYCODE</action>
          <action>if true enable:varNEW_KEY</action>
          <action>if false disable:varNEW_KEY</action>
        </radiobutton>

        <comboboxentry active="0">
          <variable>varNEW_KEY</variable>
          <input>[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f5 -d "|" | grep "key="`" != "" ] && grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f3 -d "|"; func_modmap_list</input>
        </comboboxentry>
        
        <text space-fill="true" space-expand="true"><label>" "</label></text>
        
        <radiobutton>
          <variable>varUSEKEYCODE</variable>
          <input>[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f5 -d "|" | grep "keycode="`" != "" ] && echo true || echo false</input>
          <label>'$(gettext "Keycode:")' </label>
          <action>if true enable:varNEW_KEYCODE</action>
          <action>if false disable:varNEW_KEYCODE</action>
        </radiobutton>
        
        <spinbutton range-min="0" range-max="255" range-step="1" range-value="0">
          <variable>varNEW_KEYCODE</variable>
          <input>[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f5 -d "|" | grep "keycode="`" != "" ] && grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f3 -d "|"</input>
        </spinbutton>
      
      </hbox>
    </frame>

    <frame '$(gettext "Mask")'>
      <hbox homogeneous="true">
        <checkbox>
          <variable>varNEW_A</variable>
          <label>Alt</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Alt"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_C</variable>
          <label>Ctrl</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Ctrl"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_S</variable>
          <label>Shift</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Shift"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_1</variable>
          <label>Mod1</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Mod1"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_2</variable>
          <label>Mod2</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Mod2"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_3</variable>
          <label>Mod3</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Mod3"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_4</variable>
          <label>Mod4</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Mod4"* ]] && echo true</input>
        </checkbox>
        <checkbox>
          <variable>varNEW_5</variable>
          <label>Mod5</label>
          <input>[[ "`grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f2 -d "|"`" == *"Mod5"* ]] && echo true</input>
        </checkbox>
      </hbox>
    </frame>

    <frame '$(gettext "Action")'>
      <hbox>
        <entry>
          <variable>varNEW_ACTION</variable>
          <input>grep "^${varSHORTCUT}|" '${TMP_CUTS}' | cut -f4 -d "|"</input>
        </entry>
      </hbox>
    </frame>

    <hbox>
      <button>
        <input file stock="gtk-apply"></input>
        <label>'$(gettext "Apply")'</label>
        <action>func_edit</action>
        <action>func_get_keys</action>
        <action>refresh:varSHORTCUT</action>
        <action>grabfocus:varSHORTCUT</action>
        <action>closewindow:EDIT_WINDOW</action>
      </button>
      <button>
        <input file stock="gtk-close"></input>
        <label>'$(gettext "Close")'</label>
        <action>grabfocus:varSHORTCUT</action>
        <action>closewindow:EDIT_WINDOW</action>
      </button>
    </hbox>
    
    <variable>EDIT_WINDOW</variable>
  </vbox>
</window>'

#------------------------------------------------------------------------------

export GUI_MAIN='
<window height-request="400" title="'$(gettext "JWM keyboard shortcuts")'" image-name="'${PIC_PATH}'">
  <vbox>
    <pixmap><width>56</width><input file>'${PIC_PATH}'</input></pixmap>
    <text><label>'$(gettext "You can configure your keyboard shortcuts here.")'</label></text>
    
    <vbox scrollable="true">
      <tree rules-hint="true" column-visible="false|true|true|true" exported-column="0">
        <variable>varSHORTCUT</variable>
        <label>No.|Mask|Key/Keycode|Action</label>
        <input file>'${TMP_CUTS}'</input>
        <action>launch:GUI_EDIT</action>
        <action>func_get_keys</action>
        <action>refresh:varSHORTCUT</action>
        <action signal="focus-in-event" condition="command_is_true([ ! $varSHORTCUT ] && echo true)">disable:varEDIT_STATUS</action>
        <action signal="focus-in-event" condition="command_is_true([ ! $varSHORTCUT ] && echo true)">disable:varDELETE_STATUS</action>
        <action signal="focus-in-event" condition="command_is_true([ $varSHORTCUT ] && echo true)">enable:varEDIT_STATUS</action>
        <action signal="focus-in-event" condition="command_is_true([ $varSHORTCUT ] && echo true)">enable:varDELETE_STATUS</action>
        <action signal="button-release-event">enable:varEDIT_STATUS</action>
        <action signal="button-release-event">enable:varDELETE_STATUS</action>
      </tree>
    </vbox>

    <hseparator></hseparator>
    <text><label>Note: your changes will take effect after you close this window.</label></text>

    <hbox>
      <button>
        <label>'$(gettext "Add new shortcut")'</label>
        <input file stock="gtk-new"></input>
        <action>launch:GUI_ADD</action>
      </button>

      <button>
        <variable>varEDIT_STATUS</variable>
        <label>'$(gettext "Edit selected")'</label>
        <input file stock="gtk-edit"></input>
        <action>launch:GUI_EDIT</action>
      </button>
            
      <button>
        <variable>varDELETE_STATUS</variable>
        <label>'$(gettext "Delete selected")'</label>
        <input file stock="gtk-delete"></input>
        <action>func_delete</action>
        <action>func_get_keys</action>
        <action>refresh:varSHORTCUT</action>
        <action>grabfocus:varSHORTCUT</action>
      </button>
      
      <text space-fill="true" space-expand="true"><label>"     "</label></text>
      
      <button>
        <label>'$(gettext "Help")'</label>
        <input file stock="gtk-help"></input>
        <action>func_msg INFO -file /usr/local/jwmdesk/keyConfigHelp &</action>
      </button>

      <button>
        <label>'$(gettext "Quit")'</label>
        <input file stock="gtk-quit"></input>
      </button>
    </hbox>
        
  </vbox>
</window>'

#==============================================================================
# START
#==============================================================================

[ -e "$CONFIG_FILE" ] || { func_msg ERROR "$(gettext "Configuration file doesn't exist! Aborting...")"; exit 1; }
func_get_keys

gtkdialog -p GUI_MAIN
jwm -restart
