Welcome, guest | Sign In | My Account | Store | Cart

drag and drop urls from your browser's navigation window to a tkinter widget

Python, 183 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#!/usr/bin/torify /usr/bin/python
import Tkinter
import tkFont
import requests
import html2text
import urllib2

class TkDND(object):

    def __init__(self, master):
        master.tk.eval('package require tkdnd')
        self.master = master
        self.tk = master.tk
        self._subst_format = ('%A', '%a', '%b', '%D', '%d', '%m', '%T',
                '%W', '%X', '%Y', '%x', '%y')
        self._subst_format_str = " ".join(self._subst_format)

    def bindtarget(self, window, callback, dndtype, event='<Drop>', priority=50):
        cmd = self._prepare_tkdnd_func(callback)
        return self.tk.call('dnd', 'bindtarget', window, dndtype, event,
                cmd, priority)
                
    def _prepare_tkdnd_func(self, callback):
        funcid = self.master.register(callback, self._dndsubstitute)
        cmd = ('%s %s' % (funcid, self._subst_format_str))
        return cmd               

    def _dndsubstitute(self, *args):
        if len(args) != len(self._subst_format):
            return args

        def try_int(x):
            x = str(x)
            try:
                return int(x)
            except ValueError:
                return x

        A, a, b, D, d, m, T, W, X, Y, x, y = args

        event = Tkinter.Event()
        event.action = A       # Current action of the drag and drop operation.
        event.action_list = a  # Action list supported by the drag source.
        event.mouse_button = b # Mouse button pressed during the drag and drop.
        event.data = D         # The data that has been dropped.
        event.descr = d        # The list of descriptions.
        event.modifier = m     # The list of modifier keyboard keys pressed.
        event.dndtype = T
        event.widget = self.master.nametowidget(W)
        event.x_root = X       # Mouse pointer x coord, relative to the root win.
        event.y_root = Y
        event.x = x            # Mouse pointer x coord, relative to the widget.
        event.y = y
        event.action_list = str(event.action_list).split()

        for name in ('mouse_button', 'x', 'y', 'x_root', 'y_root'):
            setattr(event, name, try_int(getattr(event, name)))
        return (event, )

class TextPlus(Tkinter.Text):
    def __init__(self, *args, **kwargs):
        Tkinter.Text.__init__(self, *args, **kwargs)
        _rc_menu_install(self)
        # overwrite default class binding so we don't need to return "break"
        self.bind_class("Text", "<Control-a>", self.event_select_all)  
        self.bind("<Button-3><ButtonRelease-3>", self.show_menu)

    def event_select_all(self, *args):
        self.focus_force()        
        self.tag_add("sel","1.0","end")

    def show_menu(self, e):
        self.tk.call("tk_popup", self.menu, e.x_root, e.y_root)

class EntryPlus(Tkinter.Entry):
    def __init__(self, *args, **kwargs):
        Tkinter.Entry.__init__(self, *args, **kwargs)
        _rc_menu_install(self, go=True)
        # overwrite default class binding so we don't need to return "break"
        self.bind_class("Entry", "<Control-a>", self.event_select_all)  
        self.bind("<Button-3><ButtonRelease-3>", self.show_menu)

    def event_select_all(self, *args):
        self.focus_force()
        self.selection_range(0, Tkinter.END)

    def event_paste_and_go(self, *args):
        self.delete(0,Tkinter.END)
        self.focus_force()
        self.event_generate("<<Paste>>")
        handlereturn(self)

    def show_menu(self, e):
        self.tk.call("tk_popup", self.menu, e.x_root, e.y_root)

def _rc_menu_install(w, go = False):
    w.menu = Tkinter.Menu(w, tearoff=0)
    w.menu.add_command(label="Cut")
    w.menu.add_command(label="Copy")
    w.menu.add_command(label="Paste")
    if go:
        w.menu.add_command(label="Paste & Go")
    w.menu.add_separator()
    w.menu.add_command(label="Select all")        

    w.menu.entryconfigure("Cut", command=lambda: w.focus_force() or w.event_generate("<<Cut>>"))
    w.menu.entryconfigure("Copy", command=lambda: w.focus_force() or w.event_generate("<<Copy>>"))
    w.menu.entryconfigure("Paste", command=lambda: w.focus_force() or w.event_generate("<<Paste>>"))
    if go:
        w.menu.entryconfigure("Paste & Go", command=w.event_paste_and_go)
    w.menu.entryconfigure("Select all", command=w.event_select_all) 

def handle(event):
    event.widget.delete(0,Tkinter.END)
    url = event.data.strip()
    event.widget.insert(0,url)
    textwindow(url)

def handlereturn(entry):
    p = entry.get().splitlines()[0]
    if not p.startswith('http'):
        p = 'file://'+ p
    textwindow(p)
    
def convert65536(s):
    #convert out-of-range characters 
    res = []
    for c in s:
        k = ord(c)
        if k < 65536:
            res.append(c)
        else:
            res.append("{"+str(k)+"?}")
    return "".join(res)
    
def gethtml(link):
    user_agent = "Mozilla/5.0 (Windows NT 6.1; rv:38.0) Gecko/20100101 Firefox/38.0"
    headers={'user-agent':user_agent}
    s = requests.Session()
    try:
        res = s.get(link,headers=headers).text
    except requests.exceptions.InvalidSchema:
        req=urllib2.Request(link,None,headers)
        r = urllib2.urlopen(req)
        res = r.read().decode('utf8')
    return res

def textwindow(url):
    title = url
    h = html2text.HTML2Text()
    h.ignore_links = True
    h.ignore_images = True
    s = gethtml(url)
    s = h.handle(s)
    s = h.unescape(s)
    text = convert65536(s)
    top = Tkinter.Toplevel()
    top.geometry("+200+100")
    top.title(title)
    top.bind("<Escape>", lambda _ : top.destroy())
    S = Tkinter.Scrollbar(top)
    customFont = tkFont.Font(family="Arial", size=16)
    T = TextPlus(top,height=20,width=78,font=customFont,bg="lightgrey")
    S.pack(side=Tkinter.RIGHT,fill=Tkinter.Y)
    T.pack(side=Tkinter.LEFT,fill=Tkinter.Y)
    S.config(command=T.yview)
    T.config(yscrollcommand=S.set)
    T.insert(Tkinter.END,text)

def main():
    root = Tkinter.Tk()
    root.geometry("950x32+200+32")
    root.title('markdown')
    dnd = TkDND(root)
    customFont = tkFont.Font(family="Arial", size=14)
    entry = EntryPlus(font=customFont,bg="lightgrey")
    entry.pack(expand=1,fill='both')
    dnd.bindtarget(entry,handle,'text/plain')
    entry.bind("<Return>", lambda _ : handlereturn(entry))
    root.mainloop()

if __name__=="__main__":
    main()

I try to block ads but firefox now has built in ads. Tor uses an older version of firefox that is a bit better so I switched to tor, which also has other benefits. But still sometimes a webpage is too difficult to navigate because of all the pics, the cramped layout to make room for the useless ads, the sidebars, the sticky tags that move with you when you scroll the page, etcetera. So I go to menu item "view - page style" and select "no style", which 9 out of 10 times is enough. But some webpages are very very stubborn.

To get those pages to display nicely too, I bypass the browser altogether, using it only for the links in the navigation bar or the bookmarks browser. This script is a kind of text-only, independent browser companion.

Make this script executable (after first installing tor, or after adapting the bang line, it can also work without tor) run it and drag and drop urls (or saved local files) into the small longish rectangle that appears.

Windows displaying the text of the files or webpages will appear below it. Take some time to change my choices for the right fonts and colors for background and text and for the initial positon and size of the windows. Maybe you also want more or less lines or longer lines.

Some day I would want to make this all configurable via the GUI but for now one has to edit the code or just accept my choices for the time being.

The TkDND class is part of a recipe I found on (I think) stackexchange, I never got Tkinter functioning as a drag and drop source to work, but it works fine as a drop target. The routine converting chars that Tkinter can't display is also adapted from something I found on the web.

It is also possible to put a link in the navigation bar using cut and paste, or by just typing it in, in that case press "return" to load the page. The text window has cut and paste functions too.