rawpython / remi

Python REMote Interface library. Platform independent. In about 100 Kbytes, perfect for your diet.
Apache License 2.0
3.53k stars 403 forks source link

Get selected text from TextInput #514

Open antwin opened 1 year ago

antwin commented 1 year ago

I need to find one of several urls in a TextInput (say with a double-click) so I can open it in a browser. I've tried

self.recTxt = gui.TextInput(single_line=False, margin='10px') self.recTxt.ondblclick.connect(self.onDblClick) self.recTxt.onmousemove.connect(self.onMouseMove)

def onDblClick(self,obj):
    print(obj.children['text'])

I can get all the text, but not find the particular url that was clicked on. Or I can get the mouse position in x,y coordinates.

How do I get the text?

dddomodossola commented 1 year ago

Hello @antwin , why are you using a TextInput to do so? wouldn't it be better to use a Link widget?

antwin commented 1 year ago

Thank you for the reply - much appreciated !

The reason is that I have an editable diary/notebook entry like:

-----------------------
remi diary part 1
https://github.com/rawpython/remi
https://remi.readthedocs.io
To tablet:
    :~>adb push py/remi/rem.py 
termux script:
python storage/external-1/rem.py
exec am start --user 0 -a android.intent.action.VIEW -n org.mozilla.firefox/org.mozilla.fenix.IntentReceiverActivity  -d "http://localhost:8082" >/dev/null
--20230419--
-------------------

This combines my notes, urls and other information. I want to set it up so that when I right-click on a url it loads the browser. Much more flexible than bookmarks! But I have to be able to edit and save the entry.

I can use remi to run on android with termux - it looks like the best way of running Python on android!

However, I can't find how to incorporate clickable urls into an editable bit of html that can then be saved.

Any suggestions?

dddomodossola commented 1 year ago

Hello @antwin ,

This is an example for you ;-)

import remi.gui as gui
from remi import start, App

"""
    This TextInputAdvanced class allows to get the caret position on click event.
"""
class TextInputAdvanced(gui.TextInput):
    @gui.decorate_event_js("""
    var params={};
    params['caretPositionStart'] = -1;
    params['caretPositionEnd'] = -1;
    if (document.activeElement == this)
    {
        try{
            params['caretPositionStart'] = document.activeElement.selectionStart;
            params['caretPositionEnd'] = document.activeElement.selectionEnd;
        }catch(e){console.debug(e.message);}
    }
    remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s', params);
    """)
    def onclick(self, caretPositionStart, caretPositionEnd):
        # The onclick event returns the text selection start and end
        # If no text is selected, -1 is returned.
        caretPositionStart = int(caretPositionStart) 
        caretPositionEnd = int(caretPositionEnd)
        return (caretPositionStart, caretPositionEnd)

class MyApp(App):
    def main(self):
        # creating a container VBox type, vertical (you can use also HBox or Widget)
        main_container = gui.VBox(style={'margin': '0px auto'})

        self.lblSelectionStartCaption = gui.Label("Selection start: ", width=100)
        self.lblSelectionEndCaption = gui.Label("Selection end: ", width=100)

        self.lblSelectionStart = gui.Label("", width=100)
        self.lblSelectionEnd = gui.Label("", width=100)

        main_container.append(gui.HBox(children=[self.lblSelectionStartCaption, self.lblSelectionStart]))
        main_container.append(gui.HBox(children=[self.lblSelectionEndCaption, self.lblSelectionEnd]))

        self.txtInput = TextInputAdvanced(False, width = 150, height = 100)
        self.txtInput.set_text("This is the url https://github.com/rawpython/remi of the remi repository.")
        self.txtInput.onclick.do(self.on_text_input_click)
        main_container.append(self.txtInput)

        # returning the root widget
        return main_container

    def on_text_input_click(self, emitter, caretPositionStart, caretPositionEnd):
        """
            Since you have the caretPositionStart, you can get the text in that position
                and if it is an url, open the url
        """
        self.lblSelectionStart.set_text(str(caretPositionStart))
        self.lblSelectionEnd.set_text(str(caretPositionEnd))

        #splitting the text by using the spaces before and after the caret
        text = self.txtInput.get_text()
        spaceBefore = -1
        try:
            spaceBefore = text.rindex(" ", 0, caretPositionStart)
        except:
            pass

        spaceAfter = len(text)
        try:
            spaceAfter = text.index(" ", caretPositionStart)
        except:
            pass

        textPointed = text[spaceBefore+1:spaceAfter]
        print("The text below the cursor is: " + textPointed)

if __name__ == "__main__":
    # starts the webserver
    start(MyApp, address='0.0.0.0', port=0, start_browser=True)
antwin commented 1 year ago

Thank you very much! That is a very useful bit of code. I have tried modding it so that a right-click (that would normally trigger a context menu) can be used instead of a left-click, but I haven't made that work yet...

dddomodossola commented 1 year ago

Hello @antwin ,

Here is the working code using the right mouse button:

import remi.gui as gui
from remi import start, App

"""
    This TextInputAdvanced class allows to get the caret position on click event.
"""
class TextInputAdvanced(gui.TextInput):
    @gui.decorate_event_js("""
    var params={};
    params['caretPositionStart'] = -1;
    params['caretPositionEnd'] = -1;
    if (document.activeElement == this)
    {
        try{
            params['caretPositionStart'] = document.activeElement.selectionStart;
            params['caretPositionEnd'] = document.activeElement.selectionEnd;
        }catch(e){console.debug(e.message);}
    }
    remi.sendCallbackParam('%(emitter_identifier)s','%(event_name)s', params);
    """)
    def oncontextmenu(self, caretPositionStart, caretPositionEnd):
        # The onclick event returns the text selection start and end
        # If no text is selected, -1 is returned.
        caretPositionStart = int(caretPositionStart) 
        caretPositionEnd = int(caretPositionEnd)
        return (caretPositionStart, caretPositionEnd)

class MyApp(App):
    def main(self):
        # creating a container VBox type, vertical (you can use also HBox or Widget)
        main_container = gui.VBox(style={'margin': '0px auto'})

        self.lblSelectionStartCaption = gui.Label("Selection start: ", width=100)
        self.lblSelectionEndCaption = gui.Label("Selection end: ", width=100)

        self.lblSelectionStart = gui.Label("", width=100)
        self.lblSelectionEnd = gui.Label("", width=100)

        main_container.append(gui.HBox(children=[self.lblSelectionStartCaption, self.lblSelectionStart]))
        main_container.append(gui.HBox(children=[self.lblSelectionEndCaption, self.lblSelectionEnd]))

        self.txtInput = TextInputAdvanced(False, width = 150, height = 100)
        self.txtInput.set_text("This is the url https://github.com/rawpython/remi of the remi repository.")
        self.txtInput.oncontextmenu.do(self.on_text_input_click, js_stop_propagation=True, js_prevent_default=True)
        main_container.append(self.txtInput)

        # returning the root widget
        return main_container

    def on_text_input_click(self, emitter, caretPositionStart, caretPositionEnd):
        """
            Since you have the caretPositionStart, you can get the text in that position
                and if it is an url, open the url
        """
        self.lblSelectionStart.set_text(str(caretPositionStart))
        self.lblSelectionEnd.set_text(str(caretPositionEnd))

        #splitting the text by using the spaces before and after the caret
        text = self.txtInput.get_text()
        spaceBefore = -1
        try:
            spaceBefore = text.rindex(" ", 0, caretPositionStart)
        except:
            pass

        spaceAfter = len(text)
        try:
            spaceAfter = text.index(" ", caretPositionStart)
        except:
            pass

        textPointed = text[spaceBefore+1:spaceAfter]
        print("The text below the cursor is: " + textPointed)

if __name__ == "__main__":
    # starts the webserver
    start(MyApp, address='0.0.0.0', port=0, start_browser=True)

Have a nice day, Davide

antwin commented 1 year ago

Thank you again for the code. I have tried the context menu version, and - following your example - a double-click version. These work fine on my debian desktop, but not on Android.

But the first (onclick) version works ok. I create a url which I can then click on, as below:


 def onRecTxtClick(self, emitter, caretPositionStart, caretPositionEnd):    
        ''' splitting the text by using the spaces before and after the caret 
            https://github.com/rawpython/remi/issues/514 '''
        text = self.recTxt.get_text()
        spaceBefore = -1
        try:
            spaceBefore = text.rindex(" ", 0, caretPositionStart)
        except:
            pass 
        spaceAfter = len(text)
        try:
            spaceAfter = text.index(" ", caretPositionStart)
        except:
            pass
        txt = text[spaceBefore+1:spaceAfter]
        if 'http' in txt or 'www' in txt:
            txt = txt.split('\n') # clean up if not enough spaces around, but linefeeds instead
            for line in txt:
                if 'http' in line or 'www' in line:
                    textPointed = line.strip()
                    break
  #      print("The text below the cursor is: " + textPointed)
#d            self.lbl.set_text(f'{textPointed}')
            self.lbl.set_text('')
            url = gui.Link(textPointed,textPointed, open_new_window=True) 
            self.urls.empty()
            self.urls.append(url)
        else:
            return