;===============================================================================
;
; Script Name: ScITe Block Editor
; Description:: Provides a GUI for simple modification of rectangular selections
; Author(s): ProgAndy
;
; -- Lines to insert in SciTEUser.properties --
; - AutoIt installed and BlockEditor in SCITEDIRECTORY\BlockEditor\BlockEditor.au3 -
; command.38.*="$(autoit3dir)\autoit3.exe" "$(SciteDefaultHome)\BlockEditor\BlockEditor.au3" "$(WindowID)"
; command.name.38.*=Block Editor
; command.save.before.38.*=0
; command.subsystem.38.*=1
; command.shortcut.38.*=Alt+C
;
; - BlockEditor compiled in SCITEDIRECTORY\BlockEditor\BlockEditor.exe -
; command.38.*="$(SciteDefaultHome)\BlockEditor\BlockEditor.exe" "$(WindowID)"
; command.name.38.*=Block Editor
; command.save.before.38.*=0
; command.subsystem.38.*=1
; command.shortcut.38.*=Alt+C
;
;===============================================================================
;
#include<GUIConstantsEx.au3>
#include<ButtonConstants.au3>
#include<ComboConstants.au3>
#include<EditConstants.au3>

#include<WinAPIEx.au3>
#include<WinAPI.au3>
#include<Sendmessage.au3>
#include<Array.au3>
#include<Math.au3>
#include<Memory.au3>

; CMDLINE[1] must contain the handle to SciteDirectorInterface
If Not $CMDLINE[0] Then
	MsgBox(64, 'BlockEditor', "Not enough parameters." & @CRLF & "Must be called via SciTE-Tools-menu" & @CRLF & _
			"Move BlockEditor.exe to SCITEDIRECTORY\BlockEditor\BlockEditor.exe and add the following lines to SciTEUser.properties:" & @CRLF & @CRLF & _
			'#------------------------------------------------------------------------------------------------' & @CRLF & _
			'command.38.*="$(SciteDefaultHome)\BlockEditor\BlockEditor.exe" "$(WindowID)"' & @CRLF & _
			"command.name.38.*=Block Editor" & @CRLF & _
			"command.save.before.38.*=0" & @CRLF & _
			"command.subsystem.38.*=1" & @CRLF & _
			"command.shortcut.38.*=Alt+C" & @CRLF & _
			'#------------------------------------------------------------------------------------------------')
	Exit
EndIf
$hWndDirector = HWnd($CMDLINE[1])

; Check whether we got a valid Director Window
$WMU_GETSCITEDIRECTOR = _WinAPI_RegisterWindowMessage("SciTEDirectorInterface")
If Not $hWndDirector Or $WMU_GETSCITEDIRECTOR <> _WinAPI_SendMessageTimeout($hWndDirector, $WMU_GETSCITEDIRECTOR) Then
	MsgBox(0, '', "No valid director interface given")
	Exit
EndIf

; Find Scintilla window for direct access without DirectorInterface
$iSciTe = WinGetProcess($hWndDirector)
Opt("WinSearchChildren", 1)
$aList = WinList("[CLASS:Scintilla; INSTANCE:1]")
$hScintilla = 0
For $i = 1 To $aList[0][0]
	If WinGetProcess($aList[$i][1]) = $iSciTe Then
		$hScintilla = $aList[$i][1]
		ExitLoop
	EndIf
Next

If Not $hScintilla Then Exit

Const $SCI_SELECTIONISRECTANGLE = 2372
Const $SCI_GETLINESELSTARTPOSITION = 2424
Const $SCI_GETLINESELENDPOSITION = 2425
Const $SCI_SETSELECTIONSTART = 2142
Const $SCI_GETSELECTIONSTART = 2143
Const $SCI_SETSELECTIONEND = 2144
Const $SCI_GETSELECTIONEND = 2145
Const $SCI_SETSELECTIONMODE = 2422
Const $SCI_GETSELECTIONMODE = 2423
Const $SCI_REPLACESEL = 2170
Const $SCI_GETSELTEXT = 2161
Const $SCI_LINEFROMPOSITION = 2166
Const $SCI_BEGINUNDOACTION = 2078
Const $SCI_ENDUNDOACTION = 2079

; First get all selections
$start = _SendMessage($hScintilla, $SCI_GETSELECTIONSTART)
$end = _SendMessage($hScintilla, $SCI_GETSELECTIONEND)
$FirstLine = _SendMessage($hScintilla, $SCI_LINEFROMPOSITION, $start)
$lastLine = _SendMessage($hScintilla, $SCI_LINEFROMPOSITION, $end)
Local $aSelections[$lastLine - $FirstLine + 1][2]
For $i = 0 To $lastLine - $FirstLine
	$aSelections[$i][0] = _SendMessage($hScintilla, $SCI_GETLINESELSTARTPOSITION, $i + $FirstLine)
	$aSelections[$i][1] = _SendMessage($hScintilla, $SCI_GETLINESELENDPOSITION, $i + $FirstLine)
Next

; then get the new Text to insert
$vReplace = _GetBlockText(UBound($aSelections), _GetSeltext($hScintilla))
If @error Then Exit

; now insert the new text, start from behind to avoid moving selection points
_SendMessage($hScintilla, $SCI_BEGINUNDOACTION)
$aMemory = _InitMem($hScintilla)
For $i = UBound($aSelections) - 1 To 0 Step -1
	_SendMessage($hScintilla, $SCI_SETSELECTIONSTART, $aSelections[$i][0])
	_SendMessage($hScintilla, $SCI_SETSELECTIONEND, $aSelections[$i][1])
	If IsArray($vReplace) Then
		_ReplaceSel($hScintilla, $vReplace[$i], $aMemory)
	Else
		_ReplaceSel($hScintilla, $vReplace, $aMemory)
	EndIf
Next
_FreeMem($aMemory)
_SendMessage($hScintilla, $SCI_ENDUNDOACTION)

Func _InitMem($hWnd, $iLen = 256)
	; ProgAndy
	If $iLen < 10 Then $iLen = 10
	Local $tMem, $aMem[3]
	$aMem[0] = $iLen
	$aMem[1] = _MemInit($hWnd, $iLen, $tMem)
	$aMem[2] = $tMem
	Return $aMem
EndFunc   ;==>_InitMem

Func _FreeMem(ByRef $aMem)
	; ProgAndy
	If Not IsArray($aMem) Then Return
	_MemFree($aMem[2])
	$aMem = 0
EndFunc   ;==>_FreeMem

Func _ReplaceSel($hWnd, $sText, $aMem = 0)
	; ProgAndy
	Local $tMem
	Local $iLen = StringLen($sText) + 1
	If IsArray($aMem) Then
		If $iLen > $aMem[0] Then
			_MemFree($aMem[1])
			$aMem[0] = $iLen
			$aMem[1] = _MemInit($hWnd, $iLen, $tMem)
			$aMem[2] = $tMem
		Else
			$tMem = $aMem[2]
		EndIf
		$pMem = $aMem[1]
	Else
		$pMem = = _MemInit($hWnd, $iLen, $tMem)
	EndIf
	_MemWrite($tMem, $sText, $pMem, $iLen, "str")
	_SendMessage($hWnd, $SCI_REPLACESEL, 0, $pMem)
	If Not IsArray($aMem) Then _MemFree($tMem)
EndFunc   ;==>_ReplaceSel

Func _GetSeltext($hWnd)
	; ProgAndy
	Local $tMem
	Local $iLen = _SendMessage($hWnd, $SCI_GETSELTEXT, 0, 0)
	Local $pMem = _MemInit($hWnd, $iLen + 1, $tMem)
	$iLen = _SendMessage($hWnd, $SCI_GETSELTEXT, 0, $pMem)
	Local $t = DllStructCreate("char[" & $iLen + 1 & "]")
	_MemRead($tMem, $pMem, DllStructGetPtr($t), $iLen + 1)
	_MemFree($tMem)
	Return DllStructGetData($t, 1)
EndFunc   ;==>_GetSeltext

Func _GetBlockText(Const $iLines, $sSelText)
	; ProgAndy
	Local $hGUI = GUICreate("SciTE Block Editor", 300, 400)
	GUIStartGroup()
	GUICtrlCreateLabel("BlockEditor for SciTE." & @CRLF & "Licensed under CC-by, a Creative Commons License" & @CRLF & "(C) by ProgAndy", 10, 5, 280, 40)
	GUICtrlSetState(-1, $GUI_DISABLE)
	Local $tab = GUICtrlCreateTab(5, 50, 290, 300)
	$tabSimple = GUICtrlCreateTabItem("&Simple")
	Local $radText = GUICtrlCreateRadio("Insert &Text", 10, 75, 200, 20)
	Local $radNumber = GUICtrlCreateRadio("Insert &Numbers", 10, 175, 200, 20)
	GUICtrlSetState($radNumber, $GUI_CHECKED)
	$iGroupText = GUICtrlCreateGroup("", 10, 95, 280, 70)
	Local $inpText = GUICtrlCreateInput("", 20, 115, 240, 20)
	GUICtrlSetState(-1, $GUI_DISABLE)
	$iGroupNum = GUICtrlCreateGroup("", 10, 195, 280, 150)
	GUICtrlCreateLabel("Start Number:", 20, 217, 80, 20)
	Local $inpNumberStart = GUICtrlCreateInput("", 100, 215, 160, 20, BitOR($ES_LEFT, $ES_AUTOHSCROLL, $ES_NUMBER))
	ControlFocus($hGUI, "", $inpNumberStart)
	GUICtrlCreateLabel("Step:", 20, 247, 80, 20)
	Local $inpNumberStep = GUICtrlCreateInput("1", 100, 245, 160, 20, BitOR($ES_LEFT, $ES_AUTOHSCROLL, $ES_NUMBER))
	Local $chkZeros = GUICtrlCreateCheckbox("&Prefix Zeros:", 20, 275, 100, 20, BitOR($BS_AUTOCHECKBOX, $BS_RIGHTBUTTON))
	GUICtrlCreateLabel("Format:", 20, 307, 80, 20)
	Local $cmbOutput = GUICtrlCreateCombo("", 100, 305, 160, 120, $CBS_DROPDOWNLIST)
	GUICtrlSetData(-1, "|Decimal|Hex with 0x|Hex|Octal", "Decimal")
	$iStop = GUICtrlCreateDummy()
	GUIStartGroup()
	Local $tabAdvanced = GUICtrlCreateTabItem("Ad&vanced")
	GUICtrlCreateLabel("Each line represents one line of the selected rectangle. If there are more lines than in the selection, they are ignored. If there are less, the remaining selection is erased.", 10, 75, 280, 45)
	Local $edtAdvanced = GUICtrlCreateEdit(StringRegExpReplace($sSelText, "(?<!\r)\n|\r(?!\n)", @CRLF), 10, 120, 280, 220)
	GUICtrlCreateTabItem("")
	Local $btnInsert = GUICtrlCreateButton("&Insert", 10, 360, 100, 30, $BS_DEFPUSHBUTTON)
	Local $btnCancel = GUICtrlCreateButton("&Cancel", 190, 360, 100, 30)
	GUISetState()
	Local $vResult, $iError = 0
	While 1
		Switch GUIGetMsg()
			Case $btnCancel, -3
				$iError = 1
				ExitLoop
			Case $radText
				For $i = $iGroupText + 1 To $iGroupNum - 1
					GUICtrlSetState($i, $GUI_ENABLE)
				Next
				For $i = $iGroupNum + 1 To $iStop - 1
					GUICtrlSetState($i, $GUI_DISABLE)
				Next
			Case $radNumber
				For $i = $iGroupNum + 1 To $iStop - 1
					GUICtrlSetState($i, $GUI_ENABLE)
				Next
				For $i = $iGroupText + 1 To $iGroupNum - 1
					GUICtrlSetState($i, $GUI_DISABLE)
				Next
			Case $btnInsert
				Switch GUICtrlRead($tab, 1)
					Case $tabAdvanced
						$vResult = StringSplit(StringStripCR(GUICtrlRead($edtAdvanced)), @LF, 2)
						ReDim $vResult[$iLines]
					Case $tabSimple
						If BitAND(GUICtrlRead($radNumber), $GUI_CHECKED) Then
							Local $vResult[$iLines]
							Local $iStart = Number(GUICtrlRead($inpNumberStart)), $iStep = Number(GUICtrlRead($inpNumberStep))
							Local $sOut = "%"
							For $i = 0 To $iLines - 1
								$vResult[$i] = $iStart + $i * $iStep
							Next
							If BitAND(GUICtrlRead($chkZeros), $GUI_CHECKED) Then $sOut &= "0" & _Max(StringLen(_ArrayMax($vResult)), StringLen(_ArrayMin($vResult)))
							Switch GUICtrlRead($cmbOutput)
								Case "Hex"
									$sOut &= "X"
								Case "Hex with 0x"
									$sOut = "0x" & $sOut & "X"
								Case "Octal"
									$sOut &= "o"
								Case Else
									$sOut &= "d"
							EndSwitch
							For $i = 0 To $iLines - 1
								$vResult[$i] = StringFormat($sOut, $vResult[$i])
							Next
						Else
							$vResult = GUICtrlRead($inpText)
						EndIf
				EndSwitch
				ExitLoop
		EndSwitch
	WEnd
	GUIDelete($hGUI)
	Return SetError($iError, 0, $vResult)
EndFunc   ;==>_GetBlockText