IUP Text Format State test and enhancement

发布时间 2023-07-11 23:31:44作者: 萝卜L

功能

  • 测试IUP Text 控件 格式(Formating)在交互中的跟随性;
  • 尝试提升格式跟随性。

涉及点

  • Text的回调顺序关联;
  • 撤销(undo)操作还原到的状态——上次手动改变光标时的状态;
  • 输入时的状态(中文输入时涉及IME);
  • k_any的C;
    CapsLock/Shift对基本键的影响,及其他修饰键的作用,
    IME激活时,C=229,参考
  • 模拟发送热键(iup.SetGlobal('KEY',..));
  • 模拟发送热键时,Text启用readonly避免受IME影响;
  • Iup的使用。

代码

组成:(可合并)

  • Iup_Text_Format_State_After_Interacting.lua
  • Test__Iup_Text_Enhance_Interactive_For_Format_Inherit.lua

环境:

  • iup 3.30,
  • lua 5.4,
  • Win 10。

Iup_Text_Format_State_After_Interacting

iup=require'iuplua'
iup.SetGlobal('UTF8MODE','YES')
Dialog=iup.dialog{
	TOPMOST='YES',
	iup.vbox{
		iup.text{
			NAME='TEXT',
			VALUE='0123456789',
			EXPAND='HORIZONTAL',
			FORMATTING='YES',
		},
		iup.button{
			NAME='Add Format Button',
			TITLE='Add Format',
			EXPAND='HORIZONTAL',
			action=function(self)
				local Text=iup.GetDialogChild(self,'TEXT')
				local Tags=iup.user{
					BULK='YES',CLEANOUT='YES',
				}
				local Text_Sections_Range_Start_To_End_Position={}
				local Text_String=Text.VALUE
				if #Text_String>=4 then
					Text_Sections_Range_Start_To_End_Position[0+1]=4
					--	1234567890
					--	 ^^^
				end
				if #Text_String>6+3 then
					Text_Sections_Range_Start_To_End_Position[#Text_String-3-1]=#Text_String-1
					--	.....67890
					--	      ^^^
				end
				for Text_Range_Start_Position,Text_Range_End_Position in pairs(Text_Sections_Range_Start_To_End_Position) do
					local Tag=iup.user{
						SELECTIONPOS=Text_Range_Start_Position..':'..Text_Range_End_Position,
						BGCOLOR='206 231 255',
					}
					Tags:append(Tag)
				end
				Text.ADDFORMATTAG=Tags
			end,
		},
	},
	map_cb=function()
		local Add_Format_Button=iup.GetDialogChild(Dialog,'Add Format Button')
		Add_Format_Button:action()
	end,
}
Dialog:show()
if iup.MainLoopLevel()==0 then
	iup.MainLoop()
end

Test__Iup_Text_Enhance_Interactive_For_Format_Inherit

--[[
	config, see `Operate_Method`.
]]
require'LuaLibs.Module.Require_Current_Folder'
--package.path=package.path..';'..string.match(arg[0],'^(.-)[^/\\]+$')..'?.lua'

local string=require'LuaLibs.String.Utf8_String'
local print=require'LuaLibs.Debug.PrintWithUserIndent'

--local Function_Chain=require'LuaLibs.Functional_Programming.Pseudo_Parallel_Function_Chain'

local iup=require'iuplua'

do--import
	iup.timer{
		TIME=1,
		action_cb=function(self)
			self.RUN='NO'
			require'Iup_Text_Format_State_After_Interacting'
			--	import global `iup`, `Dialog`.
			return iup.CLOSE
		end,--action
	}.RUN='YES'
	iup.MainLoop()
end--import

do--Text
	local Text=iup.GetDialogChild(Dialog,'TEXT')
	function Text:action(C,New_Value)
		local Old_Value=self.VALUE
		local Caret=self.CARET
		local Caret_Pos=self.CARETPOS 
		print("action:\t",
			"Old_Value:",Old_Value,"New_Value:",New_Value,--"Caret:",Caret,
			"Caret_Pos:",Caret_Pos,
			"C:",C
		)
	end--Text:action
	local Previous_Text=Text.VALUE
	--	不包含候选值。also check `Previous_Value`.
	local base_on_side,Selection_Pos_Before_Change,selected_text_before_change,caret_pos_before_change,lock_stored_caret_selection_status
	local simulate_key_press_state
	local Stored_Inserted_Value
	local text_valuechanged_cb,Text_valuechanged_cb_Invoke_Externally
	local Operate_Method=
		'select then replace'
		--	will break undo
		--	or
--		'simulate key press'
		--	有问题,也不稳定
	do--Text.caret_cb
		local invoke_times=0
		local Previous_Caret_Pos
		local Previous_Value
		--	包含输入法的候选值。also check `Previous_Text`.
		function Text:caret_cb(Lin,Col,Pos)
			local Text=Text
			local Value=Text.VALUE
			local Current_Simulate_Key_Press_State=simulate_key_press_state
			print"caret_cb:\t".I(
				--"Caret:",Lin..":"..Col,
				"Pos:",Pos,
				"Selection:",Text.SELECTEDTEXT,
				"Value:",Value,
				table.unpack(Operate_Method=='select then replace' and {"Current_Simulate_Key_Press_State:",Current_Simulate_Key_Press_State} or {})
			)
			local Previous_Text=Previous_Text
			local Next_Simulate_Key_Press_State
			if Current_Simulate_Key_Press_State then
				if (Current_Simulate_Key_Press_State=='insert value' and assert(base_on_side=='left'))
					or Current_Simulate_Key_Press_State=='restore caret' and assert(base_on_side=='right')
					then
					--simulate_key_press_state=nil
					Next_Simulate_Key_Press_State=nil
					goto Update_State
				elseif Current_Simulate_Key_Press_State=='insert value and base' then
					--simulate_key_press_state='restore caret'
					--print("handle #2 - step 4.",simulate_key_press_state)
					Next_Simulate_Key_Press_State='restore caret'
					print("handle #2 - step 4.",Next_Simulate_Key_Press_State)
					iup.SetGlobal('KEY',iup.K_LEFT)
				elseif Current_Simulate_Key_Press_State=='reset base' then
					invoke_times=invoke_times+1
					if invoke_times==3 then
						--	QUESTION: 不清楚为什么此处为'3',而非为'2'。
						--simulate_key_press_state='insert value'
						--print("handle #1 - step 4.",simulate_key_press_state)
						Next_Simulate_Key_Press_State='insert value'
						print("handle #1 - step 4.",Next_Simulate_Key_Press_State)
						Text.INSERT=Stored_Inserted_Value
						--	won't invoke `valuechanged_cb`.
						Stored_Inserted_Value=nil
						invoke_times=0
						text_valuechanged_cb=Text_valuechanged_cb_Invoke_Externally
					end
				else
					print("skip, when invoked internally by simulating key press.")
				end
				goto End
			end
			
			if Previous_Value==Value--exclude case - caret moved after `valuechanged_cb`.
				and Value==Previous_Text and not Text.SELECTEDTEXT then
				if Previous_Caret_Pos==Pos-1 then
					print"Caret move from left to right"
					base_on_side='left'
				elseif Previous_Caret_Pos==Pos+1 then
					print"Caret move from right to left"
					base_on_side='right'
				else
					base_on_side=nil
				end
			end
			
			if Previous_Text~=Value then
				lock_stored_caret_selection_status=true
				print("Lock_Stored_Caret_Selection_Status",
					"Caret_Pos:",caret_pos_before_change,
					"Selected_Text:",selected_text_before_change,
					"Selection_Pos:",Selection_Pos_Before_Change
				)
			elseif lock_stored_caret_selection_status and not Selection_Pos_Before_Change then
				print"UnLock_Stored_Caret_Selection_Status"
			end
			
			::Update_State::
			if not lock_stored_caret_selection_status then
				print"Update_State"
				Selection_Pos_Before_Change=Text.SELECTIONPOS
				caret_pos_before_change=tonumber(Text.CARETPOS)
				selected_text_before_change=Text.SELECTEDTEXT
			end
			
			Previous_Caret_Pos=Pos
			Previous_Value=Value
			
			::End::
			if Next_Simulate_Key_Press_State then
				simulate_key_press_state=Next_Simulate_Key_Press_State
			end
			print.D()
		end--Text:caret_cb
	end--caret_cb
	do--Text_valuechanged_cb_Invoke_Externally
		local Text_valuechanged_cb_Invoke_Internally do
			local Character_Of_Base
			function Text_valuechanged_cb_Invoke_Internally(self,Value,Caret_Pos)
				local Previous_Simulate_Key_Press_State=simulate_key_press_state
				print.I(
					"Previous_Simulate_Key_Press_State:",Previous_Simulate_Key_Press_State
				)
				assert(Operate_Method=='simulate key press')
				local Selected_Text_Before_Change=selected_text_before_change
				local Text=Text
				local Next_Simulate_Key_Press_State
				::Begin::
				if Previous_Simulate_Key_Press_State=='undo insert' then
					if base_on_side=='left' then
						assert(caret_pos_before_change>1 and Selected_Text_Before_Change,"Selected_Text_Before_Change: "..tostring(Selected_Text_Before_Change))
						if Text.SELECTION then
							--simulate_key_press_state='delete selection'
							--print("handle #1 - step 2a.",simulate_key_press_state)
							Next_Simulate_Key_Press_State='delete selection'
							print("handle #1 - step 2a.",Next_Simulate_Key_Press_State)
							iup.SetGlobal('KEY',iup.K_BS)
						else--when IME
							--simulate_key_press_state='insert value'
							--print("handle #1 - step 2b.",simulate_key_press_state)
							Next_Simulate_Key_Press_State='insert value'
							print("handle #1 - step 2b.",Next_Simulate_Key_Press_State)
							Text.INSERT=Stored_Inserted_Value
							--	won't invoke `valuechanged_cb`.
							Stored_Inserted_Value=nil
							text_valuechanged_cb=Text_valuechanged_cb_Invoke_Externally
							--	--TODO: 连续时出错, need update something in `caret_cb` ?
							Selection_Pos_Before_Change=nil
							selected_text_before_change=nil
							print"end."
						end
					elseif base_on_side=='right' then
						--simulate_key_press_state='delete base'
						--print("handle #2 - step 2.",simulate_key_press_state)
						Next_Simulate_Key_Press_State='delete base'
						print("handle #2 - step 2.",Next_Simulate_Key_Press_State)
						assert(tonumber(Text.COUNT)>Caret_Pos)
						Character_Of_Base=string.sub(Value,Caret_Pos+1,Caret_Pos+1)
						print("Character_Of_Base:",Character_Of_Base)
						iup.SetGlobal('KEY',iup.K_RIGHT)
						iup.SetGlobal('KEY',iup.K_BS)
					else error'unhandle.'
					end
				elseif Previous_Simulate_Key_Press_State=='delete selection' then
					--simulate_key_press_state='reset base'
					--print("handle #1 - step 3.",simulate_key_press_state)
					Next_Simulate_Key_Press_State='reset base'
					print("handle #1 - step 3.",Next_Simulate_Key_Press_State)
					assert(base_on_side=='left')
					iup.SetGlobal('KEY',iup.K_LEFT)
					iup.SetGlobal('KEY',iup.K_RIGHT)
					--'handle #1 - step 4.' see `caret_cb`.
				elseif Previous_Simulate_Key_Press_State=='insert value' then
					error'never be invoked.'
					text_valuechanged_cb=Text_valuechanged_cb_Invoke_Externally
				elseif Previous_Simulate_Key_Press_State=='delete base' then
					assert(base_on_side=='right')
					--simulate_key_press_state='insert value and base'
					--print("handle #2 - step 3.",simulate_key_press_state)
					Next_Simulate_Key_Press_State='insert value and base'
					print("handle #2 - step 3.",Next_Simulate_Key_Press_State)
					Text.INSERT=Stored_Inserted_Value..Character_Of_Base
					--	won't invoke `valuechanged_cb`?
					Character_Of_Base=nil
					Stored_Inserted_Value=nil
					text_valuechanged_cb=Text_valuechanged_cb_Invoke_Externally
					--'handle #2 - step 4.' see `caret_cb`.
				elseif Previous_Simulate_Key_Press_State=='restore caret' then
				else error("unhandle. Previous_Simulate_Key_Press_State:"..tostring(Previous_Simulate_Key_Press_State))
				end
				if Next_Simulate_Key_Press_State then
					simulate_key_press_state=Next_Simulate_Key_Press_State
				end
				print.D()
			end--Text_valuechanged_cb_Invoke_Internally
		end--Text_valuechanged_cb_Invoke_Internally
		local function Delete_Insert_Value(Caret_Pos,Inserted_Value)
			local Text=Text
			local Insert_Value_Left_Pos=Selection_Pos_Before_Change and string.match(Selection_Pos_Before_Change,'(%d+):') or caret_pos_before_change
			local Insert_Value_Right_Pos=Caret_Pos
			if not Text.SELECTEDTEXT==Inserted_Value then
				print("Text.SELECTEDTEXT:",Text.SELECTEDTEXT,
					"Inserted_Value:",Inserted_Value
				)
			end
			Text.SELECTIONPOS=Insert_Value_Left_Pos..':'..Insert_Value_Right_Pos
			Text.SELECTEDTEXT=''
			return Insert_Value_Left_Pos,Insert_Value_Right_Pos
		end--Delete_Insert_Value()
		function Text_valuechanged_cb_Invoke_Externally(self,Value,Caret_Pos)
			local Caret_Pos_Before_Change=caret_pos_before_change
			local Selected_Text_Before_Change=selected_text_before_change
			local Inserted_Value do
				if Selected_Text_Before_Change then
					local Left_Pos=tonumber(string.match(Selection_Pos_Before_Change,'(%d+):'))
					Inserted_Value=string.sub(Value,Left_Pos+1,Caret_Pos)
				else
					Inserted_Value=string.sub(Value,Caret_Pos_Before_Change+1,Caret_Pos)
				end
			end
			print.I(
				"Inserted_Value:",Inserted_Value,
				"Caret_Pos_Before_Change:",Caret_Pos_Before_Change,
				table.unpack(Selected_Text_Before_Change and {
					"Selected_Text_Before_Change:",Selected_Text_Before_Change,
					"Selection_Pos_Before_Change:",Selection_Pos_Before_Change
				} or {})
			)
			
			local Text=Text
			if Operate_Method=='simulate key press' and #Inserted_Value>0 then
				repeat
					if base_on_side=='left' then
						if Caret_Pos_Before_Change>1 and Selected_Text_Before_Change then
							goto Handle
						end
					elseif base_on_side=='right' then
						if --not Selected_Text_Before_Change and
							tonumber(Text.COUNT)>Caret_Pos then
							goto Handle
						end
					end
					break
					::Handle::
					simulate_key_press_state='undo insert'
					print("handle step 1.",simulate_key_press_state)
					text_valuechanged_cb=Text_valuechanged_cb_Invoke_Internally
					Stored_Inserted_Value=Inserted_Value
					print("Stored_Inserted_Value:",Stored_Inserted_Value)
					Text.READONLY='YES'
					iup.SetGlobal('KEY',iup.XkeyCtrl(iup.K_z))
					Text.READONLY='NO'
				until true
			elseif Operate_Method=='select then replace' then
				if base_on_side=='left' then
					if Caret_Pos_Before_Change>1 and Selected_Text_Before_Change then
						print"handle #1"
						local Insert_Value_Left_Pos,Insert_Value_Right_Pos=Delete_Insert_Value(Caret_Pos,Inserted_Value)
						Text.SELECTIONPOS=(Insert_Value_Left_Pos-1)..':'..Insert_Value_Left_Pos
						local Character_At_Left=Text.SELECTEDTEXT
						Text.SELECTEDTEXT=Character_At_Left..Inserted_Value
						Text.CARETPOS=Insert_Value_Right_Pos
					else--default, skip.
					end
				elseif base_on_side=='right' then
					if tonumber(Text.COUNT)>Caret_Pos then
						print"handle #2"
						local Insert_Value_Left_Pos,Insert_Value_Right_Pos=Delete_Insert_Value(Caret_Pos,Inserted_Value)
						Text.SELECTIONPOS=Insert_Value_Left_Pos..':'..(Insert_Value_Left_Pos+1)
						local Character_At_Right=Text.SELECTEDTEXT
						Text.SELECTEDTEXT=Inserted_Value..Character_At_Right
						Text.CARETPOS=Insert_Value_Right_Pos
					else
						--use left.
					end
				end
			end
			Previous_Text=Value
			
			if--undo will select something after handle
				Operate_Method=='select then replace' and
				Inserted_Value=='' and Text.SELECTION then
				print"fix caret in undo, after handle - which change selection."
				--local Selection_Left_Pos=string.match(Text.SELECTIONPOS,'(%d+):')
				--Text.CARETPOS=Selection_Left_Pos
		--		Text.CARETPOS=Base_On_Side=='right' and Caret_Pos-1 or Caret_Pos
			end
			
			if lock_stored_caret_selection_status
				then
				lock_stored_caret_selection_status=false
				print"UnLock_Stored_Caret_Selection_Status"
				
				--operation follow-up may not initial these state, so do it here.
				caret_pos_before_change=Caret_Pos
				Selection_Pos_Before_Change=nil
				selected_text_before_change=nil
			end
			print.D()
		end--Text_valuechanged_cb_Invoke_Externally
		text_valuechanged_cb=Text_valuechanged_cb_Invoke_Externally
	end--Text_valuechanged_cb_Invoke_Externally
	function Text:valuechanged_cb()
		local Value=self.VALUE
		local Caret_Pos=tonumber(self.CARETPOS)
		print("valuechanged_cb:",
			"Value:",Value,
			"Caret_Pos:",Caret_Pos
		)
		return text_valuechanged_cb(self,Value,Caret_Pos)
	end
	do--Text.k_any
		local Parse_K_Any_C_Key_Name=require'LuaLibs.IUP.Interaction.Parse_K_Any_C_Key_Name'
		function Text:k_any(C)
			print("k_any:",C).I()
			local Text=Text
			if C==iup.K_ESC then
				print"K_ESC"
				local Selected_Text_Before_Change=selected_text_before_change
				if lock_stored_caret_selection_status and Selected_Text_Before_Change then
					print"restore previous stored caret selection status"
					Text.INSERT=Selected_Text_Before_Change
					--	won't invoke `valuechanged_cb`?
					Text.CARETPOS=caret_pos_before_change
					Text.SELECTIONPOS=Selection_Pos_Before_Change
					
					--	--Selected_Text_Before_Change,Caret_Pos_Before_Change,Selection_Pos_Before_Change=nil
					--	should not clear, for won't update state before `caret_cb`, but new input could be occurs.
				end
			elseif C==iup.K_BS then
				print"K_BS"
				if Operate_Method=='simulate key press' then
					iup.SetGlobal("KEY",iup.K_UP)
					--	set new restore point for undo.
				end
			else
				print(Parse_K_Any_C_Key_Name(C))
			end
			print.D()
		end--Text:k_any
	end--Text.k_any
	function Text:getfocus_cb()
		print"getfocus_cb"
	end
	function Text:killfocus_cb()
		--print"killfocus_cb"
	end
end--Text

do--Add_Format_Button
	local Add_Format_Button=iup.GetDialogChild(Dialog,'Add Format Button')
	Add_Format_Button:action()
end

iup.MainLoop()