Page 1 of 1

Typewriter Simulator for Scintilla Controls

Posted: Fri Jun 07, 2024 3:16 pm
by Axolotl
I would say the subject/headline says it all.
Others would say another pointless and useless program from me......
No matter, as long as it's fun.

Seriously, because of a question in the german forum, I quickly put something together.
Edit: Small update (added Notepad++) to the supported editors

Code: Select all

; -----------------------------------------------------------------------------
; Proudly presented by Axolotl 
; -----------------------------------------------------------------------------
EnableExplicit 

Enumeration EWindow 1 ; start with 1 (ZERO can be used for error detecting) 
  #WINDOW_Main 
EndEnumeration 

Enumeration EGadget 1 ; start with 1 (ZERO can be used for error detecting) 
  #GADGET_btnStart 
  #GADGET_btnStop 
  #GADGET_cbbEditors 
  #GADGET_btnFind 
  #GADGET_edtCode 
  #GADGET_trbSpeed 
EndEnumeration 

Enumeration EEvent #PB_Event_FirstCustomValue 
  #EVENT_Begin 
  #EVENT_Finished 
EndEnumeration

Enumeration EStatusbar 1 
  #STATUSBAR 
EndEnumeration 

; some constants .... 
; 
#MainCaption$     = "Typewriter Simulator for Scintilla Controls " ; + #PB_Editor_BuildCount + "." + #PB_Editor_CompileCount 
#MainWindowFlags  = #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_SizeGadget  


; ---------------------------------------------------------------------------------------------------------------------

Global hwndEditor, hwndScintilla          ; 
Global ClassTextEditor$, TitleEditor$     ; find the correct editor 
Global TextFormat                         ; get Codepage .. ASCII or UTF8 
Global TextToSend$                        ; 
Global TypeTextStopped, TypeTextDelay     ; 
Global QuitApp = #False 


; -----------------------------------------------------------------------------
; >> PureBasic, Notepad++, SciTE 
; 
Procedure EnumChildProcEditors(hWnd, lParam) 
  Protected text${#MAX_PATH}  

	If hWnd 
		GetClassName_(hwnd, @text$, #MAX_PATH) 
		If text$ = ClassTextEditor$ 
			GetWindowText_(hwnd, @text$, #MAX_PATH) 

			If TitleEditor$ Or FindString(text$, TitleEditor$) 
				hwndEditor = hWnd 
				ProcedureReturn 0   ; stop enumeration 
			EndIf
		EndIf
		ProcedureReturn 1   ; continue enumeration 
	EndIf
	ProcedureReturn 0   ; stop enumeration 
EndProcedure 

; -----------------------------------------------------------------------------
; >> Scintilla  
; 
Procedure EnumChildProcScintilla(hWnd, lParam) 
  Protected text${#MAX_PATH}  

	If hWnd
		GetClassName_(hWnd, @text$, #MAX_PATH)
		If text$ = "Scintilla" 
			hwndScintilla = hWnd 
			ProcedureReturn 0   ; stop enumeration 
		EndIf
		ProcedureReturn 1   ; continue enumeration 
	EndIf
	ProcedureReturn 0   ; stop enumeration 
EndProcedure

; -----------------------------------------------------------------------------

Procedure FindEditorWithScintillaControl(Which=0) 
  ; 

  Select Which  
    Case 0 ; 0 .. Notepad2 
    	TitleEditor$      = "Notepad2" 
    	ClassTextEditor$  = "Notepad2" 
    Case 1 ; 1 .. Purebasic 
    	TitleEditor$      = "PureBasic"  
    	ClassTextEditor$  = "WindowClass_2" 
    Case 2 ; 0 .. Notepad++ 
    	TitleEditor$      = "Notepad++" 
    	ClassTextEditor$  = "Notepad++" 
    Default 
      ProcedureReturn #False  ; failed 
  EndSelect 

  ; enum the child windows 
  ; 
	EnumChildWindows_(0, @EnumChildProcEditors(), 0)
	If hwndEditor 
  	EnumChildWindows_(hwndEditor, @EnumChildProcScintilla(), 0) 

		If hwndScintilla 
  		Select SendMessage_(hwndScintilla, #SCI_GETCODEPAGE, #Null, #Null) 
  			Case 0     : TextFormat = #PB_Ascii   :Debug "TextFormat == ASCII" 
  			Case 65001 : TextFormat = #PB_UTF8    :Debug "TextFormat == UTF8" 
  		EndSelect 
      ProcedureReturn #True ; succeeded  
    EndIf 
  EndIf 
  

  ProcedureReturn #False  ; failed 
EndProcedure 

; -----------------------------------------------------------------------------

Procedure GetProcessFromWindow(hWnd)  
	Protected hProcess   

	If GetWindowThreadProcessId_(hWnd, @hProcess) 
		ProcedureReturn OpenProcess_(#PROCESS_ALL_ACCESS, #False, hProcess) 
	EndIf 
	ProcedureReturn 0 
EndProcedure

; -----------------------------------------------------------------------------

Procedure SendText(Text$) 
	Protected hProcess, length
	Protected *MemoryID, *Buffer, *Text ; format

  hProcess = GetProcessFromWindow(hwndScintilla) 

	If hProcess 
		length = StringByteLength(Text$, TextFormat) 
		*Buffer = AllocateMemory(length + SizeOf(Character)) 
		If *Buffer 
			PokeS(*Buffer, Text$, #PB_Default, TextFormat) 
			*MemoryID = VirtualAllocEx_(hProcess, #Null, Length, #MEM_RESERVE|#MEM_COMMIT, #PAGE_EXECUTE_READWRITE)
			If *MemoryID
				WriteProcessMemory_(hProcess, *MemoryID, *Buffer, Length, #Null)
				SendMessage_(hwndScintilla, #SCI_ADDTEXT, Length, *MemoryID) 
				VirtualFreeEx_(hProcess, *MemoryID, length, #MEM_RELEASE) 
			EndIf 
			FreeMemory(*Buffer) 
		EndIf
		CloseHandle_(hProcess) 
	EndIf 
EndProcedure 

; -----------------------------------------------------------------------------

Procedure ThreadedTextSending(*Value) 
  Protected state, index, count, word, length, textline$, text$   

  PostEvent(#EVENT_Begin)

  index = 1                     ; start 
  length = Len(TextToSend$)     ; end 

  While index <= length 
    If QuitApp = #True : Break : EndIf ; user has clicked the close button 
    If TypeTextStopped = #False 
      text$ = Mid(TextToSend$, index, 1) 
      SendText(text$) 
      index + 1 
    EndIf 
;   Delay(TypeTextDelay) 
    Delay(Random(TypeTextDelay/10, 10)) 
  Wend 

  PostEvent(#EVENT_Finished) 
EndProcedure 


; --- OnEvent_ function -------------------------------------------------------

Procedure OnEvent_SizeWindow() 
  Protected ww, wh 

  ww = WindowWidth(#WINDOW_Main) : wh = WindowHeight(#WINDOW_Main) 

  ResizeGadget(#GADGET_trbSpeed, ww - 160, #PB_Ignore, #PB_Ignore, #PB_Ignore) 
  ResizeGadget(#GADGET_edtCode, #PB_Ignore, #PB_Ignore, ww - 8, wh - 40) 
EndProcedure 

; --- Main Window -------------------------------------------------------------

Procedure CreateMainWindow(WndW = 600, WndH = 600)  
  If OpenWindow(#WINDOW_Main, 0, 0, WndW, WndH, #MainCaption$, #MainWindowFlags) 
    StickyWindow(#WINDOW_Main, 1) 

    CreateStatusBar(#STATUSBAR, WindowID(#WINDOW_Main)) 
    AddStatusBarField(#PB_Ignore) 
    AddStatusBarField(120) 
    WndH - StatusBarHeight(#STATUSBAR) 

    ButtonGadget(#GADGET_btnStart, 4, 4, 72, 24, "Start")  
    ButtonGadget(#GADGET_btnStop, 80, 4, 72, 24, "Stop") 

    ComboBoxGadget(#GADGET_cbbEditors, 160, 4, 104, 24) 
      AddGadgetItem(#GADGET_cbbEditors, -1, "Notepad2") 
      AddGadgetItem(#GADGET_cbbEditors, -1, "Purebasic") 
      AddGadgetItem(#GADGET_cbbEditors, -1, "Notepad++") 
   
    ButtonGadget(#GADGET_btnFind, 268, 4, 72, 24, "Find Editor")  

    TrackBarGadget(#GADGET_trbSpeed, WndW-160,  4, 152, 24, 1, 20)  

    EditorGadget(#GADGET_edtCode, 4, 32, WndW-8, WndH-40) 

    BindEvent(#PB_Event_SizeWindow, @OnEvent_SizeWindow(), #WINDOW_Main) 

  EndIf 
  ProcedureReturn IsWindow(#WINDOW_Main)  ; non-zero if valid window 
EndProcedure 

Macro ButtonUpdateTyping(State)  
  DisableGadget(#GADGET_btnStart, 1-State) 
  DisableGadget(#GADGET_btnStop, State) 
EndMacro 

Macro ButtonUpdateLookForEditors(State) 
  DisableGadget(#GADGET_cbbEditors, State) 
  DisableGadget(#GADGET_btnFind, State) 
EndMacro 

; --- main() ------------------------------------------------------------------

Procedure main() 
  Protected state, text$ 
  Protected Thread  
 
  If CreateMainWindow() 

    ButtonUpdateTyping(0) 

    SetGadgetState(#GADGET_trbSpeed, 10)  ; init 
    PostEvent(#PB_Event_Gadget, #WINDOW_Main, #GADGET_trbSpeed) ; TypeTextDelay = 20 

    SetGadgetState(#GADGET_cbbEditors, 0) 

    SetGadgetText(#GADGET_edtCode, ";" + #LF$ + 
                                   "; Test Code " + #LF$ +  
                                   "; Paste some Code here ... " + #LF$ +  
                                   "; 1. look for your Editor " + #LF$ +  
                                   "; 2. start the typing " + #LF$ +  
                                   "; " + #LF$ +  
                                   "; You can start and stop the typing at any time. " + #LF$ +  
                                   "" + #LF$ +  
                                   "; BoF") ; end of code 
    ; 
   
    Repeat  ; .. main loop 
      Select WaitWindowEvent(250)  
        Case #PB_Event_None 

        Case #PB_Event_CloseWindow  ; close button on main window --> close application 
          If EventWindow() = #WINDOW_Main 
            QuitApp = #True 
            If IsThread(Thread) 
              If WaitThread(Thread, 2000) = 0   ; timeout was reached 
                KillThread(Thread)              ; stop the thread right now 
              EndIf 
            EndIf 
            Break   ; bye 
          EndIf 

        Case #EVENT_Begin   ; Update GUI 
          ButtonUpdateTyping(0) 
          ButtonUpdateLookForEditors(1) 

        Case #EVENT_Finished   ; Update GUI 
          Beep_(200, 200) 
          ButtonUpdateTyping(1) 
          ButtonUpdateLookForEditors(0) 
          TypeTextStopped = #False 

        Case #PB_Event_Gadget 
          Select EventGadget()
            Case #GADGET_btnStart 
              If TypeTextStopped = #False 
                TextToSend$ = GetGadgetText(#GADGET_edtCode)  ; get the entire text 
                Thread = CreateThread(@ThreadedTextSending(), 0) 
              Else 
                TypeTextStopped = #False 
                ButtonUpdateTyping(0) 
              EndIf 

            Case #GADGET_btnStop 
              TypeTextStopped = #True 
              ButtonUpdateTyping(1) 

            Case #GADGET_btnFind 
              state = GetGadgetState(#GADGET_cbbEditors) 
              text$ = GetGadgetText(#GADGET_cbbEditors) 
              If FindEditorWithScintillaControl(state) 
                StatusBarText(#STATUSBAR, 0, "Editor: " + text$ + " (" + hwndEditor + ", Scintilla = " + hwndScintilla + ") found. ") 
                ButtonUpdateTyping(1) 
              Else 
                StatusBarText(#STATUSBAR, 0, "Editor: " + text$ + " (?, Scintilla = ?) not found. ") 
                ButtonUpdateTyping(0) 
              EndIf 

            Case #GADGET_trbSpeed 
              TypeTextDelay = GetGadgetState(#GADGET_trbSpeed) * 100  ; 
              StatusBarText(#STATUSBAR, 1, "Speed: " + Str(TypeTextDelay/10) + " ") 

          EndSelect 
      EndSelect
    ForEver ; end of main loop 
  EndIf 
EndProcedure 

; --- start -----------------------------------------------------------------
End main() 

Re: Typewriter Simulator for Scintilla Controls

Posted: Fri Jun 07, 2024 5:33 pm
by PeDe
Thanks for nice useless program.
I tested with Notepad2 5.0.26-beta4, and had to change the value for 'ClassTextEditor$' to 'Notepad2U'.

Peter

Re: Typewriter Simulator for Scintilla Controls

Posted: Fri Jun 07, 2024 6:21 pm
by spikey
Notepad++ can already do this for itself, see https://npp-user-manual.org/docs/ghost-typing/

My favourite is:
DEBUGGING /diːˈbʌɡɪŋ/ noun - The classic mystery game where you are the detective, the victim and the murderer.

Re: Typewriter Simulator for Scintilla Controls

Posted: Fri Jun 07, 2024 7:53 pm
by tft
Hallo

"There are no useless projects. I have just written a tool that does exactly 2 things: data backup from X to Y, from 4 sources to 2 targets, as part of my current project. In line with my Twitch TV ambitions, I am looking for the possibility to have text written into the PureBasic editor. This is for representation purposes."

TFT

Re: Typewriter Simulator for Scintilla Controls

Posted: Thu Jun 27, 2024 7:50 pm
by Kwai chang caine
Works nice for IDE PB here
Thanks for this "Useless" program, who can be UseFull for me :mrgreen:

Re: Typewriter Simulator for Scintilla Controls

Posted: Mon Aug 05, 2024 10:36 am
by boddhi
It may not be useful specifically for this code, but just in case you want to know if the typing was successful.

Modification of Main() procedure only
Add to variable declarations section:

Code: Select all

Protected.i TextLengthBefore,TextLengthAfter
Then replace #EVENT_Begin and #EVENT_Finished cases

Code: Select all

      Case #EVENT_Begin                         ; Update GUI
        ; •••••••• ADD START ••••••••
        TextLengthBefore = SendMessage_(hwndScintilla, #SCI_GETTEXTLENGTH, 0, 0)
        ; •••••••• ADD END ••••••••
        Pc_ButtonUpdateTyping(#False)
        Pc_ButtonUpdateLookForEditors(#True)
      Case #EVENT_Finished                      ; Update GUI
        Beep_(200, 200)
        Pc_ButtonUpdateTyping(#True)
        Pc_ButtonUpdateLookForEditors(#False)
        TypeTextStopped = #False
        ; •••••••• ADD START ••••••••
        TextLengthAfter=SendMessage_(hwndScintilla, #SCI_GETTEXTLENGTH, 0, 0)
        Select Bool(TextLengthAfter>TextLengthBefore)
          Case #True
            MessageRequester("Result", "Complete typing !", #PB_MessageRequester_Info)
          Case #False
            MessageRequester("Result", "No typing !", #PB_MessageRequester_Error)
        EndSelect
        TextLengthbefore = 0:TextLengthAfter = 0
        ; •••••••• ADD END ••••••••
 
Test it in "Integrated IDE debugger" and "Compile without debugger" modes.