Community
    • Login

    Editor right-click, outside current text selection, lose this selection

    Scheduled Pinned Locked Moved Notepad++ & Plugin Development
    35 Posts 6 Posters 10.3k Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • frying-panF
      frying-pan
      last edited by

      As stated, my next operation on selected text woud be (for example) a copy,
      which is not possible if the selection has been lost…

      After a few hours of pythonscript quick learning, it seems to work as intended. Kind of miracle.
      Someone could have a look to see if there are errors, or if improvements could be added.

      Code adapted from
      https://community.notepad-plus-plus.org/post/15238
      https://community.notepad-plus-plus.org/post/47293

      # PythonScript that prevent right-click to move the caret and lose current text selection
      # My first PythonScript, so there could be errors...
      # Tested on 64 bits NPP (NOT tested on 32 Bits but could be compatible)
      
      from Npp import *
      
      import platform
      import ctypes
      from ctypes import wintypes
      import datetime
      from datetime import datetime
      
      LRESULT = wintypes.LPARAM
      
      WndProcType = ctypes.WINFUNCTYPE(LRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
      
      CallWindowProc = ctypes.windll.user32.CallWindowProcW
      CallWindowProc.restype = LRESULT
      CallWindowProc.argtypes = [WndProcType, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
      
      x86 = platform.architecture()[0] == '32bit'
      if x86:
          SetWindowLong = ctypes.windll.user32.SetWindowLongW
      else:
          SetWindowLong = ctypes.windll.user32.SetWindowLongPtrW
      SetWindowLong.restype = WndProcType
      SetWindowLong.argtypes = [wintypes.HWND, wintypes.INT, WndProcType]
      
      FindWindow = ctypes.windll.user32.FindWindowW     # used to find class='Notepad++' npp window handle
      FindWindowEx = ctypes.windll.user32.FindWindowExW # used to find class='scintilla' child window handle
      
      GWL_WNDPROC = -4        # used to set a new address for the window procedure
      WM_RBUTTONDOWN = 0x0204 # message we are interested in
      
      class Mouse_Right_Click_Hook(): # hook class
      
          def __init__(self):                                                           # class constructor
              self.nppHandle = FindWindow(u'Notepad++', None)                           # find NPP window handle and store it
                                                                                        # possibly ambiguous, there could be multiple windows : class='Notepad++'
              self.scintHandle = FindWindowEx(self.nppHandle, None, u'Scintilla', None) # find 'scintilla' child window handle and store it
                                                                                        # there is more than 1 (4 of them in fact, 3 with no text and hidden) !
                                                                                        # how is it I seem to always get the right one ?
                                                                                        # maybe because the right one is always the first one in the Z-order (luck)
              print 'NPPHandle=' + hex(self.nppHandle)
              print 'ScintillaHandle=' + hex(self.scintHandle)
      
          def register(self):                                                                 # function to register the hook
              self.newWndProc = WndProcType(self.MyWndProc)                                   # get the address of our wndproc
              self.oldWndProc = SetWindowLong(self.scintHandle, GWL_WNDPROC, self.newWndProc) # register it and receive old wndproc address
              console.editor.setProperty('oldWndProc', self.oldWndProc)                       # store it to be able to unregister if needed
              #print self.newWndProc
              #print self.oldWndProc
      
          def MyWndProc(self, hWnd, msg, wParam, lParam):                       # our own wndproc function receives windows messages
              if msg == WM_RBUTTONDOWN:                                         # it is WM_RBUTTONDOWN msg
                  #print 'Mouse_Right_Click_Hook'
                  #print datetime.now()
                  return CallWindowProc(self.oldWndProc, hWnd, 0, 0, 0)         # kill the standard right-click message
              return CallWindowProc(self.oldWndProc, hWnd, msg, wParam, lParam) # IMPORTANT pass other received msg to npp, otherwise npp will be blocked.
      
      print '*** Mouse_Right_Click_Hook starts... ***'
      print datetime.now()
      _mouse_right_click_hook = Mouse_Right_Click_Hook() # create an instance of Mouse_Right_Click_Hook class
      _mouse_right_click_hook.register()                 # set up the mouse hook
      print '*** Mouse_Right_Click_Hook registered ***'
      
      EkopalypseE 1 Reply Last reply Reply Quote 0
      • Alan KilbornA
        Alan Kilborn
        last edited by

        @frying-pan said in Editor right-click, outside current text selection, lose this selection:

        As stated, my next operation on selected text woud be (for example) a copy,
        which is not possible if the selection has been lost…

        It makes no sense. If you want to copy the selected text, right click the selected text…

        1 Reply Last reply Reply Quote 1
        • László BotkaL
          László Botka
          last edited by

          I think it makes sense, at least for me and for the OP.
          And probably for others too, because as I see, it works in Notepad, Wordpad, Ultraedit, although doesn’t work in Winword.

          1 Reply Last reply Reply Quote 0
          • guy038G
            guy038
            last edited by guy038

            Hello, @frying-pan, @alan-kilborn, @lászló-botka and All,

            @lászló-botka, you said :

            I think it makes sense, at least for me and for the OP.

            So, @frying-pan, @lászló-botka :

            Okay, but what good is that to you ? I mean : why is right-clicking outside the selection area interesting to you ?

            WHAT do you intend to do, after right-clicking outside the selection area ? Could you give us a concrete example ?

            Like @alan-kilborn, I don’t see any immediate advantage in doing things your way ?!

            Best Regards,

            guy038

            1 Reply Last reply Reply Quote 0
            • László BotkaL
              László Botka
              last edited by

              For example I select the text, that I perhaps want to Copy/Cut, then I watch the text moving the mouse until find the text, where I want to copy to, right click Copy/Cut, left click at the desired text on the screen where I want to copy, and right click Paste.

              This is hard enough to describe for me, but easy to do.

              It gives me a lot of freedom, I don’t have to issue “Copy”, only when and where it is needed, so I don’t overwrite the clipboard possibly unnecessarily.

              Naturally right-clicking inside the selection area works already, nobody have to work on it - I understand this thinking.

              1 Reply Last reply Reply Quote 1
              • Alan KilbornA
                Alan Kilborn
                last edited by Alan Kilborn

                Regardless, its a Scintilla characteristic, not a Notepad++ characteristic…

                BTW, I have no clue as to what this means:

                I select the text, that I perhaps want to Copy/Cut, then I watch the text moving the mouse until find the text, where I want to copy to, right click Copy/Cut, left click at the desired text on the screen where I want to copy, and right click Paste.

                1 Reply Last reply Reply Quote 0
                • guy038G
                  guy038
                  last edited by

                  Hello, @frying-pan, @alan-kilborn, @lászló-botka and All,

                  @lászló-botka, you said :

                  For example I select the text, that I perhaps want to Copy/Cut, then I watch the text moving the mouse until find the text, where I want to copy to, right click Copy/Cut, left click at the desired text on the screen where I want to copy, and right click Paste.

                  OK, I see ! Personally, I would have used, both, keyboard and mouse :

                  • Selection of text to be copied elsewhere

                  • Hit on the Ctrl + C shortcut to place selection into clipboard

                  • Move of the mouse to the other location where you want to paste the selection

                  • Left mouse click to specify the new location of the caret

                  • Right mouse click to get the context menu

                  • Left mouse click on the Paste option


                  However, to be honest, I’m more a “keyboard-gestures” man than a “mouse-gestures” one ;-))

                  Best regards

                  guy038

                  1 Reply Last reply Reply Quote 1
                  • Alan KilbornA
                    Alan Kilborn
                    last edited by

                    Okay, so after reading @guy038’s response to:

                    I select the text, that I perhaps want to Copy/Cut, then I watch the text moving the mouse until find the text, where I want to copy to, right click Copy/Cut, left click at the desired text on the screen where I want to copy, and right click Paste.

                    it helped me to understand it, I think.

                    If I were doing it that way, I’d have zero confidence that the source block I was copying/cutting was still selected. Much better to have it onscreen (which the current Notepad++ enforces) when copying/cutting it.

                    But I agree with @guy038 about mouse-vs-keyboard, and here are the actions compared:

                    mouse :

                    • move hand from keyboard to mouse (this is a text editor after all)
                    • right click
                    • choose copy
                    • return hand to keyboard (this is a text editor after all)

                    keyboard :

                    • press ctrl+c

                    Even mouse purists (if there are any) would have a hard time arguing with the effort involved. :-)

                    1 Reply Last reply Reply Quote 0
                    • EkopalypseE
                      Ekopalypse @frying-pan
                      last edited by

                      @frying-pan

                      Note, that you only set the hook for one scintilla message queue but
                      I assume you want it to have for both, don’t you?

                      Beside that everything is personal coding preferences, just be carefully to call it once.

                      1 Reply Last reply Reply Quote 0
                      • guy038G
                        guy038
                        last edited by

                        Hi, @alan-kilborn,

                        You said :

                        BTW, I have no clue as to what this means:

                        To my mind, here are the different operations, described by @lászló-botka :

                        • First, you select, with the mouse, some text to be pasted elsewhere

                        • Then you search for the location where to paste the selection. moving the vertical bar of the window up/down to reach the new location ( the selection, which may be out of sight, is still active )

                        • Now, as you are around the desired location, you right-click to get the context menu

                        • Then, you left-click on the Copy option, which places present selection into clipboard ( This is the step where Notepad and Notepad++ behaviors differ ! )

                        • Now, you left-click at the location where you want the contents of clipboard to be inserted

                        • Again, you right-click to get the context menu

                        • Finally, you left-click and choose the Paste option

                        Best Regards,

                        guy038

                        Alan KilbornA László BotkaL 2 Replies Last reply Reply Quote 2
                        • Alan KilbornA
                          Alan Kilborn @guy038
                          last edited by

                          @guy038 said in Editor right-click, outside current text selection, lose this selection:

                          To my mind, here are the different operations, described by @lászló-botka :

                          Yes, I understood this from your earlier clarification. Maybe you had this reply in progress before seeing my earlier reply.

                          I just still find it odd, but to each his own.

                          1 Reply Last reply Reply Quote 0
                          • László BotkaL
                            László Botka @guy038
                            last edited by

                            @guy038 exactly, thank you for making it clear.

                            Alan KilbornA 1 Reply Last reply Reply Quote 2
                            • Alan KilbornA
                              Alan Kilborn @László Botka
                              last edited by

                              @László-Botka

                              I see, it works in Notepad, Wordpad, Ultraedit, although doesn’t work in Winword.

                              Another notable is Visual Studio 2019, where it works exactly like Notepad++ does.

                              1 Reply Last reply Reply Quote 1
                              • László BotkaL
                                László Botka
                                last edited by

                                I have a rarely used editor installed, Pspad, and it works again as Winword, Notepad ++, Visual Studio 2019.

                                I see no reason why one program behaves this way and the other that way. Maybe it’s occasional, it works the way it just did, or the way it was natural, simple for the programmer in the development tool.

                                Since I use Firefox a lot, it’s natural for me to select something, and if I move the mouse by any reason or accidentally, and then do a right click, the selection remains.

                                Alan KilbornA 1 Reply Last reply Reply Quote 2
                                • Alan KilbornA
                                  Alan Kilborn @László Botka
                                  last edited by

                                  @László-Botka said in Editor right-click, outside current text selection, lose this selection:

                                  have a rarely used editor installed, Pspad

                                  PsPad is cool! :-)

                                  I like to think of it this way: If I select something with the mouse, and I want to drag-n-drop-move-it, I can’t just point anywhere and click-drag, I have to point to the selected text first. So right-click-copy is analogous.

                                  But yes, points and all views about this are well-taken. Good discussions are what this forum is about.

                                  1 Reply Last reply Reply Quote 2
                                  • frying-panF
                                    frying-pan
                                    last edited by

                                    I wonder if explaining why it is very useful to me is needed.

                                    first example : (I copy a lot between different tabs and different applications)
                                    Some text is selected in a notepad++ tab, or any application window, that I need to copy repeatedly.
                                    I activate this tab or window with ctrl+tab/alt+tab, right-click anywhere and ‘copy’,
                                    go back to the other app and right-click anywhere and ‘paste’.
                                    So, I do not have to point every time the selection or the caret for copying/pasting.

                                    second example :
                                    I select some text in a notepad++ tab with the mouse, and immediatly right-click to copy it.
                                    If I have moved just a pixel farther than the selection, then I lose the selection and can not copy it.

                                    Added benefit : Since I can right-click anywhere, I do not have the context-menu appearing above the selection,
                                    so I can check my selection is good…

                                    indeed Microsoft Visual Studio (when editing Microsoft Word macro) is one of the infamous selection-looser
                                    Internet Explorer 11 also…

                                    ===

                                    Here is the improved code

                                    • code has been hardened against errors
                                    • the hook still can not be removed (*see below the code why),
                                      but now can be “de-activated”/“re-activated” by re-running the script
                                    • now hooks all scintilla windows (so incuding the notepad++ second view when splitted)
                                    • added : SHIFT + double left mouse click -> select text from click point until next space/space-like chars

                                    (sorry the two functions SHIFT+double-left-click and right-click can not be “de-activated” separately,
                                    I think a dialog prompt and two separate variables set by console.editor.SetProperty would do that)

                                    # PythonScript which hooks mouse clicks (WndProc HOOK on Scintilla windows)
                                    # * SHIFT + double left mouse click : select text from click point until next space/space-like chars : \s
                                    # * right mouse click : prevent right-click from moving the caret and losing current text selection
                                    # re-run the script to de-activate/re_activate the hook
                                    # Tested on NPP 64 bits (NOT tested on 32 Bits but could be compatible), /!\ this file uses TABS for indent
                                    
                                    from Npp import *
                                    
                                    import platform
                                    import ctypes
                                    from ctypes import wintypes
                                    import datetime
                                    from datetime import datetime
                                    
                                    s_script_name			= "Perso_ScintWndProc_Hook"
                                    s_hook_name				= "Scint_Mouse_Click_Hook"
                                    s_editorprop_regdone	= "SCINTWNDPROCHOOK_DONE"
                                    s_editorprop_active		= "SCINTWNDPROCHOOK_ACTIVE"
                                    s_true					= "TRUE"
                                    
                                    GWL_WNDPROC = -4				# used to set a new address for the windows procedure
                                    I_VK_SHIFT		= 0x10			# SHIFT virtual key code
                                    I_VK_CONTROL	= 0x11			# CONTROL virtual key code
                                    I_VK_ALT		= 0x12			# ALT virtual key code
                                    KSTATE_ISDOWN = 0x8000			# key pressed
                                    WM_LBUTTONDBLCLK	= 0x0203	# double left mouse click window message
                                    WM_RBUTTONDOWN		= 0x0204	# right mouse click window message
                                    
                                    LRESULT = wintypes.LPARAM
                                    WndProcType = ctypes.WINFUNCTYPE(LRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
                                    
                                    # window message hook functions
                                    CallWindowProc = ctypes.windll.user32.CallWindowProcW
                                    CallWindowProc.restype = LRESULT
                                    CallWindowProc.argtypes = [WndProcType, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
                                    
                                    x86 = platform.architecture()[0] == "32bit"
                                    if x86:
                                    	SetWindowLong = ctypes.windll.user32.SetWindowLongW
                                    else:
                                    	SetWindowLong = ctypes.windll.user32.SetWindowLongPtrW
                                    SetWindowLong.restype = WndProcType
                                    SetWindowLong.argtypes = [wintypes.HWND, wintypes.INT, WndProcType]
                                    # end of window message hook functions
                                    
                                    EnumWindowsProc				= ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
                                    EnumWindows					= ctypes.windll.user32.EnumWindows
                                    EnumChildWindows			= ctypes.windll.user32.EnumChildWindows
                                    RealGetWindowClass			= ctypes.windll.user32.RealGetWindowClassW
                                    GetWindowThreadProcessId	= ctypes.windll.user32.GetWindowThreadProcessId
                                    GetCurrentProcessId			= ctypes.windll.kernel32.GetCurrentProcessId
                                    GetLastError				= ctypes.windll.kernel32.GetLastError
                                    SetLastError				= ctypes.windll.kernel32.SetLastError
                                    
                                    GetAsyncKeyState = ctypes.windll.user32.GetAsyncKeyState # used to check if a modifier key is pressed
                                    
                                    class C_Scint_Mouse_Click_Hook(): # hook class
                                    	# class constructor
                                    	def __init__(self):
                                    		def Enum_Window_Hwnd(hwnd, lParam):
                                    			arr_enum_window_hwnd.append(hwnd)
                                    			return True
                                    
                                    		s_npp_class		= u"Notepad++"
                                    		s_scint_class	= u"Scintilla"
                                    
                                    		self.arr_scint_hwnd = []
                                    		self.npp_win_hwnd = 0
                                    
                                    		i_cur_pid = GetCurrentProcessId()
                                    
                                    		arr_enum_window_hwnd = []
                                    		EnumWindows(EnumWindowsProc(Enum_Window_Hwnd), 0)
                                    		# enum all class="Notepad++" NPP window handles
                                    		buff = ctypes.create_unicode_buffer(len(s_npp_class) + 1 + 1)
                                    		for win_hwnd in arr_enum_window_hwnd:
                                    			RealGetWindowClass(win_hwnd, buff, len(s_npp_class) + 1 + 1)
                                    			if buff.value == s_npp_class:
                                    				# check that found NPP window handle is owned by current process, stop at the first found
                                    				ci_win_pid = wintypes.DWORD(0)
                                    				GetWindowThreadProcessId(win_hwnd, ctypes.pointer(ci_win_pid))
                                    				if i_cur_pid == ci_win_pid.value:
                                    					self.npp_win_hwnd = win_hwnd
                                    					break
                                    		if self.npp_win_hwnd == 0:
                                    			return
                                    
                                    		del arr_enum_window_hwnd[:]
                                    		EnumChildWindows(self.npp_win_hwnd, EnumWindowsProc(Enum_Window_Hwnd), 0)
                                    		# enum all class="Scintilla" window handles, childs of NPP window
                                    		# get all of them in arr_scint_hwnd (at least all existing ones at that time)
                                    		buff = ctypes.create_unicode_buffer(len(s_scint_class) + 1 + 1)
                                    		for win_hwnd in arr_enum_window_hwnd:
                                    			RealGetWindowClass(win_hwnd, buff, len(s_scint_class) + 1 + 1)
                                    			if buff.value == s_scint_class:
                                    				self.arr_scint_hwnd.append(win_hwnd)
                                    
                                    	def RegHook(self):															# function to register the hook on all found Scintilla windows
                                    		s_npp_class		= u"Notepad++"
                                    		s_scint_class	= u"Scintilla"
                                    
                                    		if (self.npp_win_hwnd == 0 or len(self.arr_scint_hwnd) == 0):			# abort if no NPP handle or no Scintilla handle
                                    			return
                                    
                                    		self.NewWndProc = WndProcType(self.MyWndProc)							# get the address of our own WndProc
                                    
                                    		self.arr_scint_oldwndproc = []
                                    		s_list = "\t" + "Found " + str(len(self.arr_scint_hwnd)) + " " + s_scint_class + " Handle / WindowProc"
                                    		for i in range(0, len(self.arr_scint_hwnd)):
                                    			win_hwnd = self.arr_scint_hwnd[i]
                                    			SetLastError(0)
                                    			oldwndproc = SetWindowLong(win_hwnd, GWL_WNDPROC, self.NewWndProc)	# register and store oldwndproc addresses in arr_scint_oldwndproc
                                    			i_apierr = GetLastError()
                                    			if i_apierr == 0:
                                    				self.arr_scint_oldwndproc.append(oldwndproc)
                                    				s_list = s_list + "\n" + "\t\t" + hex(win_hwnd) + " / " + str(oldwndproc)
                                    			else:
                                    				del self.arr_scint_hwnd[i]
                                    				s_list = s_list + "\n" + "\t\t" + hex(win_hwnd) + " / " + "NO WindowProc, NOT hooked"
                                    		print "\t" + s_npp_class + " Handle = " + hex(self.npp_win_hwnd) + "\n" + s_list
                                    
                                    	def MyWndProc(self, hWnd, msg, wParam, lParam):							# our own WndProc function receives windows messages
                                    		oldwndproc = 0
                                    		for i in range(0, len(self.arr_scint_hwnd)):
                                    			if self.arr_scint_hwnd[i] == hWnd:								# target hWnd found at index i in arr_scint_hwnd
                                    				oldwndproc = self.arr_scint_oldwndproc[i]					# corresponding oldwndproc is picked in arr_scint_oldwndproc
                                    				break
                                    		if oldwndproc == 0:													# fatal error ! should not happen...
                                    			print "\t" + s_hook_name + " Fatal error ! oldwndproc = 0"
                                    			notepad.messageBox(s_hook_name + " Fatal error ! oldwndproc = 0", s_script_name)
                                    			return 0
                                    
                                    		if (msg != WM_LBUTTONDBLCLK and msg != WM_RBUTTONDOWN):				# if NOT mouse hooked messages : abort
                                    			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                    		if console.editor.getProperty(s_editorprop_active) != s_true:		# if hook de-activated : abort
                                    			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                    
                                    		b_shift_down	= ((GetAsyncKeyState(I_VK_SHIFT)	& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                    		b_ctrl_down		= ((GetAsyncKeyState(I_VK_CONTROL)	& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                    		b_alt_down		= ((GetAsyncKeyState(I_VK_ALT)		& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                    		if (b_shift_down and b_ctrl_down and b_alt_down):					# if SHIFT and CONTROL and ALT down : unregister
                                    			self.UnRegHook()
                                    			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                    
                                    		if (msg == WM_LBUTTONDBLCLK and b_shift_down and not b_ctrl_down and not b_alt_down):
                                    			self.ExtendSelToSpaceLikeChars()								# extend selection from the clicked point to the space-like chars
                                    		elif (msg == WM_RBUTTONDOWN and not b_shift_down and not b_ctrl_down and not b_alt_down):
                                    			dummy = 0														# do nothing
                                    		else:
                                    			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                    
                                    		#res = "DEBUG " + s_hook_name
                                    		#res = res + " Message = " + hex(msg) + " to hWnd = " + hex(hWnd)
                                    		#res = res + " Forwarded To oldwndproc = " + str(oldwndproc)
                                    		#print res + " At " + str(datetime.now())
                                    		return CallWindowProc(oldwndproc, hWnd, 0, 0, 0)					# nullify the mouse hooked messages
                                    
                                    	def ExtendSelToSpaceLikeChars(self):
                                    		# space-like chars are space/tab/carriage return/line feed/form feed... [\t\n\x0B\f\r\x20\x85\xA0\x{2028}\x{2029}]
                                    		editor.searchAnchor()
                                    		i_start = editor.searchPrev(FINDOPTION.REGEXP, "\s")
                                    		i_start = i_start + 1
                                    		if i_start < 0:
                                    			i_start = 0
                                    		i_end = editor.searchNext(FINDOPTION.REGEXP, "\s")
                                    		if i_end < 0:
                                    			i_end = editor.getTextLength()
                                    		editor.setSel(i_start, i_end)
                                    
                                    print "[" + s_script_name + " starts]"
                                    if console.editor.getProperty(s_editorprop_regdone) != s_true:
                                    	console.editor.setProperty(s_editorprop_regdone, s_true)
                                    	console.editor.setProperty(s_editorprop_active, s_true)
                                    	o_scint_mouse_click_hook = C_Scint_Mouse_Click_Hook()	# create an instance of hook class
                                    	o_scint_mouse_click_hook.RegHook()						# set up the mouse hook
                                    	print "\t" + s_hook_name + " registered and ACTIVATED (re-run script to toggle the hook)"
                                    else:
                                    	if console.editor.getProperty(s_editorprop_active) != s_true:
                                    		console.editor.setProperty(s_editorprop_active, s_true)
                                    		print "\t" + s_hook_name + " RE-ACTIVATED (re-run script to toggle the hook)"
                                    		notepad.messageBox(s_hook_name + " RE-ACTIVATED\n(re-run script to toggle the hook)", s_script_name)
                                    	else:
                                    		console.editor.setProperty(s_editorprop_active, "")
                                    		print "\t" + s_hook_name + " de-activated (re-run script to toggle the hook)"
                                    		notepad.messageBox(s_hook_name + " de-activated\n(re-run script to toggle the hook)", s_script_name)
                                    
                                    

                                    *Breaking the hook chain :
                                    If a hook is set by

                                    self.NewWndProc = WndProcType(self.MyWndProc)
                                    oldwndproc = SetWindowLong(win_hwnd, GWL_WNDPROC, self.NewWndProc)
                                    

                                    and then removed by

                                    SetWindowLong(win_hwnd, GWL_WNDPROC, oldwndproc)
                                    

                                    that breaks the hook chain if there is more than 1 hook.

                                    original WndProc funcA > hooked by MyWndProc funcB > hooked by MyOtherWndProc funcC
                                    if funcB hook is removed, than funcC will not receive any message after that, and will not be aware of it

                                    Some more sophisticated hook api could solve that problem but I don’t know them.
                                    I chose the easiest way, which is to “de-activate” my hook by just telling him to do nothing,
                                    except silently forwarding the window message to the the next WndProc (oldwndproc).
                                    This way my “de-activated” hook does nothing but does not break the hook chain.

                                    Alan KilbornA frying-panF 2 Replies Last reply Reply Quote 2
                                    • Alan KilbornA
                                      Alan Kilborn @frying-pan
                                      last edited by

                                      @frying-pan said in Editor right-click, outside current text selection, lose this selection:

                                      I wonder if explaining why it is very useful to me is needed.

                                      Not needed, but often I give people that at first seem to want odd things a “hard time” about it, and it draws them out into explaining/justifying their need.

                                      Future readers that come along then get the whole story, and maybe don’t dismiss something that could really be of use.

                                      I think you have done a good job of that, as well as solving your own problem with the script. Nicely done!

                                      frying-panF 1 Reply Last reply Reply Quote 1
                                      • frying-panF
                                        frying-pan @Alan Kilborn
                                        last edited by frying-pan

                                        @Alan-Kilborn

                                        Well, I would not have made Notepad++ my default text editor, if I couldn’t have solved the right-click question.

                                        It’s the first reason I bothered to install PythonScript and learn this syntax (while I’m only accustomed to Pascal/VB/VBS/AutoIt)

                                        And otherwise I use PSPad for its Project features, Notepad++ session and workspace are not as featured.
                                        PSPad is mostly missing code wrapping (if > endif, etc…)

                                        1 Reply Last reply Reply Quote 1
                                        • frying-panF
                                          frying-pan
                                          last edited by

                                          Oops, I forgot to delete 3 lines of old code (at line 141 of the script). But I can’t modify any more the previous post.

                                          So the code updated without the bug :

                                          # PythonScript which hooks mouse clicks (WndProc HOOK on Scintilla windows)
                                          # * SHIFT + double left mouse click : select text from click point until next space/space-like chars : \s
                                          # * right mouse click : prevent right-click from moving the caret and losing current text selection
                                          # re-run the script to de-activate/re-activate the hook
                                          # Tested on NPP 64 bits (NOT tested on 32 Bits but could be compatible), /!\ this file uses TABS for indent
                                          
                                          from Npp import *
                                          
                                          import platform
                                          import ctypes
                                          from ctypes import wintypes
                                          import datetime
                                          from datetime import datetime
                                          
                                          s_script_name			= "Perso_ScintWndProc_Hook"
                                          s_hook_name				= "Scint_Mouse_Click_Hook"
                                          s_editorprop_regdone	= "SCINTWNDPROCHOOK_DONE"
                                          s_editorprop_active		= "SCINTWNDPROCHOOK_ACTIVE"
                                          s_true					= "TRUE"
                                          
                                          GWL_WNDPROC = -4				# used to set a new address for the windows procedure
                                          I_VK_SHIFT		= 0x10			# SHIFT virtual key code
                                          I_VK_CONTROL	= 0x11			# CONTROL virtual key code
                                          I_VK_ALT		= 0x12			# ALT virtual key code
                                          KSTATE_ISDOWN = 0x8000			# key pressed
                                          WM_LBUTTONDBLCLK	= 0x0203	# double left mouse click window message
                                          WM_RBUTTONDOWN		= 0x0204	# right mouse click window message
                                          
                                          LRESULT = wintypes.LPARAM
                                          WndProcType = ctypes.WINFUNCTYPE(LRESULT, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM)
                                          
                                          # window message hook functions
                                          CallWindowProc = ctypes.windll.user32.CallWindowProcW
                                          CallWindowProc.restype = LRESULT
                                          CallWindowProc.argtypes = [WndProcType, wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
                                          
                                          x86 = platform.architecture()[0] == "32bit"
                                          if x86:
                                          	SetWindowLong = ctypes.windll.user32.SetWindowLongW
                                          else:
                                          	SetWindowLong = ctypes.windll.user32.SetWindowLongPtrW
                                          SetWindowLong.restype = WndProcType
                                          SetWindowLong.argtypes = [wintypes.HWND, wintypes.INT, WndProcType]
                                          # end of window message hook functions
                                          
                                          EnumWindowsProc				= ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
                                          EnumWindows					= ctypes.windll.user32.EnumWindows
                                          EnumChildWindows			= ctypes.windll.user32.EnumChildWindows
                                          RealGetWindowClass			= ctypes.windll.user32.RealGetWindowClassW
                                          GetWindowThreadProcessId	= ctypes.windll.user32.GetWindowThreadProcessId
                                          GetCurrentProcessId			= ctypes.windll.kernel32.GetCurrentProcessId
                                          GetLastError				= ctypes.windll.kernel32.GetLastError
                                          SetLastError				= ctypes.windll.kernel32.SetLastError
                                          
                                          GetAsyncKeyState = ctypes.windll.user32.GetAsyncKeyState # used to check if a modifier key is pressed
                                          
                                          class C_Scint_Mouse_Click_Hook(): # hook class
                                          	# class constructor
                                          	def __init__(self):
                                          		def Enum_Window_Hwnd(hwnd, lParam):
                                          			arr_enum_window_hwnd.append(hwnd)
                                          			return True
                                          
                                          		s_npp_class		= u"Notepad++"
                                          		s_scint_class	= u"Scintilla"
                                          
                                          		self.arr_scint_hwnd = []
                                          		self.npp_win_hwnd = 0
                                          
                                          		i_cur_pid = GetCurrentProcessId()
                                          
                                          		arr_enum_window_hwnd = []
                                          		EnumWindows(EnumWindowsProc(Enum_Window_Hwnd), 0)
                                          		# enum all class="Notepad++" NPP window handles
                                          		buff = ctypes.create_unicode_buffer(len(s_npp_class) + 1 + 1)
                                          		for win_hwnd in arr_enum_window_hwnd:
                                          			RealGetWindowClass(win_hwnd, buff, len(s_npp_class) + 1 + 1)
                                          			if buff.value == s_npp_class:
                                          				# check that found NPP window handle is owned by current process, stop at the first found
                                          				ci_win_pid = wintypes.DWORD(0)
                                          				GetWindowThreadProcessId(win_hwnd, ctypes.pointer(ci_win_pid))
                                          				if i_cur_pid == ci_win_pid.value:
                                          					self.npp_win_hwnd = win_hwnd
                                          					break
                                          		if self.npp_win_hwnd == 0:
                                          			return
                                          
                                          		del arr_enum_window_hwnd[:]
                                          		EnumChildWindows(self.npp_win_hwnd, EnumWindowsProc(Enum_Window_Hwnd), 0)
                                          		# enum all class="Scintilla" window handles, childs of NPP window
                                          		# get all of them in arr_scint_hwnd (at least all existing ones at that time)
                                          		buff = ctypes.create_unicode_buffer(len(s_scint_class) + 1 + 1)
                                          		for win_hwnd in arr_enum_window_hwnd:
                                          			RealGetWindowClass(win_hwnd, buff, len(s_scint_class) + 1 + 1)
                                          			if buff.value == s_scint_class:
                                          				self.arr_scint_hwnd.append(win_hwnd)
                                          
                                          	def RegHook(self):															# function to register the hook on all found Scintilla windows
                                          		s_npp_class		= u"Notepad++"
                                          		s_scint_class	= u"Scintilla"
                                          
                                          		if (self.npp_win_hwnd == 0 or len(self.arr_scint_hwnd) == 0):			# abort if no NPP handle or no Scintilla handle
                                          			return
                                          
                                          		self.NewWndProc = WndProcType(self.MyWndProc)							# get the address of our own WndProc
                                          
                                          		self.arr_scint_oldwndproc = []
                                          		s_list = "\t" + "Found " + str(len(self.arr_scint_hwnd)) + " " + s_scint_class + " Handle / WindowProc"
                                          		for i in range(0, len(self.arr_scint_hwnd)):
                                          			win_hwnd = self.arr_scint_hwnd[i]
                                          			SetLastError(0)
                                          			oldwndproc = SetWindowLong(win_hwnd, GWL_WNDPROC, self.NewWndProc)	# register and store oldwndproc addresses in arr_scint_oldwndproc
                                          			i_apierr = GetLastError()
                                          			if i_apierr == 0:
                                          				self.arr_scint_oldwndproc.append(oldwndproc)
                                          				s_list = s_list + "\n" + "\t\t" + hex(win_hwnd) + " / " + str(oldwndproc)
                                          			else:
                                          				del self.arr_scint_hwnd[i]
                                          				s_list = s_list + "\n" + "\t\t" + hex(win_hwnd) + " / " + "NO WindowProc, NOT hooked"
                                          		print "\t" + s_npp_class + " Handle = " + hex(self.npp_win_hwnd) + "\n" + s_list
                                          
                                          	def MyWndProc(self, hWnd, msg, wParam, lParam):							# our own WndProc function receives windows messages
                                          		oldwndproc = 0
                                          		for i in range(0, len(self.arr_scint_hwnd)):
                                          			if self.arr_scint_hwnd[i] == hWnd:								# target hWnd found at index i in arr_scint_hwnd
                                          				oldwndproc = self.arr_scint_oldwndproc[i]					# corresponding oldwndproc is picked in arr_scint_oldwndproc
                                          				break
                                          		if oldwndproc == 0:													# fatal error ! should not happen...
                                          			print "\t" + s_hook_name + " Fatal error ! oldwndproc = 0"
                                          			notepad.messageBox(s_hook_name + " Fatal error ! oldwndproc = 0", s_script_name)
                                          			return 0
                                          
                                          		if (msg != WM_LBUTTONDBLCLK and msg != WM_RBUTTONDOWN):				# if NOT mouse hooked messages : abort
                                          			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                          		if console.editor.getProperty(s_editorprop_active) != s_true:		# if hook de-activated : abort
                                          			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                          
                                          		b_shift_down	= ((GetAsyncKeyState(I_VK_SHIFT)	& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                          		b_ctrl_down		= ((GetAsyncKeyState(I_VK_CONTROL)	& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                          		b_alt_down		= ((GetAsyncKeyState(I_VK_ALT)		& KSTATE_ISDOWN) == KSTATE_ISDOWN)
                                          
                                          		if (msg == WM_LBUTTONDBLCLK and b_shift_down and not(b_ctrl_down) and not(b_alt_down)):
                                          			self.ExtendSelToSpaceLikeChars()								# extend selection from the clicked point to the space-like chars
                                          		elif (msg == WM_RBUTTONDOWN and not(b_shift_down) and not(b_ctrl_down) and not(b_alt_down)):
                                          			dummy = 0														# do nothing
                                          		else:
                                          			return CallWindowProc(oldwndproc, hWnd, msg, wParam, lParam)	# -> IMPORTANT pass other msg to NPP, otherwise will block NPP
                                          
                                          		#res = "DEBUG " + s_hook_name
                                          		#res = res + " Message = " + hex(msg) + " to hWnd = " + hex(hWnd)
                                          		#res = res + " Forwarded To oldwndproc = " + str(oldwndproc)
                                          		#print res + " At " + str(datetime.now())
                                          		return CallWindowProc(oldwndproc, hWnd, 0, 0, 0)					# nullify the mouse hooked messages
                                          
                                          	def ExtendSelToSpaceLikeChars(self):
                                          		# space-like chars are space/tab/carriage return/line feed/form feed... [\t\n\x0B\f\r\x20\x85\xA0\x{2028}\x{2029}]
                                          		editor.searchAnchor()
                                          		i_start = editor.searchPrev(FINDOPTION.REGEXP, "\s")
                                          		i_start = i_start + 1
                                          		if i_start < 0:
                                          			i_start = 0
                                          		i_end = editor.searchNext(FINDOPTION.REGEXP, "\s")
                                          		if i_end < 0:
                                          			i_end = editor.getTextLength()
                                          		editor.setSel(i_start, i_end)
                                          
                                          print "[" + s_script_name + " starts]"
                                          if console.editor.getProperty(s_editorprop_regdone) != s_true:
                                          	console.editor.setProperty(s_editorprop_regdone, s_true)
                                          	console.editor.setProperty(s_editorprop_active, s_true)
                                          	o_scint_mouse_click_hook = C_Scint_Mouse_Click_Hook()	# create an instance of hook class
                                          	o_scint_mouse_click_hook.RegHook()						# set up the mouse hook
                                          	print "\t" + s_hook_name + " registered and ACTIVATED (re-run script to toggle the hook)"
                                          else:
                                          	if console.editor.getProperty(s_editorprop_active) != s_true:
                                          		console.editor.setProperty(s_editorprop_active, s_true)
                                          		print "\t" + s_hook_name + " RE-ACTIVATED (re-run script to toggle the hook)"
                                          		notepad.messageBox(s_hook_name + " RE-ACTIVATED\n(re-run script to toggle the hook)", s_script_name)
                                          	else:
                                          		console.editor.setProperty(s_editorprop_active, "")
                                          		print "\t" + s_hook_name + " de-activated (re-run script to toggle the hook)"
                                          		notepad.messageBox(s_hook_name + " de-activated\n(re-run script to toggle the hook)", s_script_name)
                                          
                                          
                                          1 Reply Last reply Reply Quote 0
                                          • frying-panF
                                            frying-pan @frying-pan
                                            last edited by frying-pan

                                            This post is deleted!
                                            1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            The Community of users of the Notepad++ text editor.
                                            Powered by NodeBB | Contributors