Package: teek
TeekTeek is a pythonic and user-friendly alternative to tkinter. It doesn't come
with Python so you need to install it yourself, but it's nice and light-weight.Documentation:https://teek.rtfd.org/Teek is PythonicIf you have worked with tkinter a lot, you know that it's kind of annoying.
Almost everything is represented as strings in Tcl. Tkinter is dumb and it
doesn't try to do things like they would be usually done in Python; instead,
tkinter users need to deal with many inconveniences themselves. On the other
hand, Teek ispythonic; it does things like they are best done in Python,
not how they are done in Tcl.TtkNever heard of Ttk before? Shame on you. Ttk is the new way to write GUIs in
Tk, and you should be already using it in tkinter. Ttk GUIs look alotbetter
than non-Ttk GUIs on most platforms. For example, this GUI has a Ttk button and
a non-Ttk button. Guess which is which:The problem is that Tk's windows (in tkinter,tkinter.Toplevelandtkinter.Tk) arenotTtk widgets. If you add Ttk widgets into them, the GUI
looks messy on some systems, like my linux system with MATE desktop. The
solution is to add a big Ttk frame that fills the window, and then add all
widgets into that frame.Tkinter:# this is a well-done hello world, and tbh, most people don't use tkinter "well"importtkinterfromtkinterimportttkroot=tkinter.Tk()big_frame=ttk.Frame(root)big_frame.pack(fill='both',expand=True)# make sure it fills the root windowttk.Label(big_frame,text="Hello World!").pack()root.mainloop()Teek:importteekwindow=teek.Window("Hello")teek.Label(window,"Hello World!").pack()window.on_delete_window.connect(teek.quit)teek.run()All teek widgets are Ttk, so you don't need to do a separate import to use
ttk widgets. Also, when you create a teekWindow, the big ttk frame is
created and packed automatically for you, and you don't need to think about it
at all; you just create aWindowand add stuff into it.ThreadsHeretime.sleeps represent blocking things. In real life you could e.g. do
network requests, run a subprocess or perform CPU-sensitive computations in the
thread.Tkinter:importqueueimportthreadingimporttimeimporttkinterroot=tkinter.Tk()root.title("Thread Demo")text=tkinter.Text(root)text.pack()message_queue=queue.Queue()defqueue_poller():whileTrue:try:message=message_queue.get(block=False)exceptqueue.Empty:breaktext.insert('end',message)root.after(50,queue_poller)defthread_target():message_queue.put('doing things...\n')time.sleep(1)message_queue.put('doing more things...\n')time.sleep(2)message_queue.put('done')threading.Thread(target=thread_target).start()queue_poller()root.mainloop()Teek:importthreadingimporttimeimportteektext=teek.Text(teek.Window("Thread Demo"))text.pack()defthread_target():text.insert(text.end,'doing things...\n')time.sleep(1)text.insert(text.end,'doing more things...\n')time.sleep(2)text.insert(text.end,'done')teek.init_threads()threading.Thread(target=thread_target).start()window.on_delete_window.connect(teek.quit)teek.run()This is not a joke. Using threads with tkinter is a horrible mess, but teek
works with threads nicely. All you need isteek.init_threads(), and then you
can do teek things from threads. Seeconcurrency docsfor details.DebuggabilityTkinter:label=ttk.Label(some_widget,text="hello world")print(label)# prints something like '.140269016152776', which is confusingprint(repr(label))# somewhat better: <tkinter.ttk.Label object .140269016152776>Teek:label=teek.Label(some_widget,"hello world")print(label)# <teek.Label widget: text='hello world'>Text Widget IndexesThe 4th character of the 3rd line of a text widget is the string'3.4'in
Tcl and tkinter. This is not only confusing because3.4looks like a float
even though treating it as a float messes things up, but this makes code look
messy.Tkinter:# figure out where the cursor isline,column=textwidget.index('insert').split('.')line=int(line)column=int(column)# same thing, more conciseline,column=map(int,textwidget.index('insert').split('.'))Teek:line,column=textwidget.marks['insert']textwidget.marksis a dictionary-like object with mark names as keys and text
index namedtuples as values. Teek represents text indexes as namedtuples
that havelineandcolumnattributes, which is useful if you only need the
line. In tkinter, you need to parse the'line.column'string with.split('.')and take the first element of the split result.Tkinter:cursor_lineno=int(textwidget.index('insert').split('.')[0])Teek:cursor_lineno=textwidget.marks['insert'].lineIn tkinter you also need to construct the'line.column'strings yourself, but
in teek you can use(line, column)tuples.Tkinter:textwidget.mark_set('insert','{}.{}'.format(new_cursor_line,new_cursor_column))Teek:textwidget.marks['insert']=(new_cursor_line,new_cursor_column)Tcl uses strings like3.4 + 5 charsto denote the position that is 5
characters after the position3.4. Teek's text position namedtuples have a
pythonicforward()method that returns a new text position.Tkinter:# textwidget.index always returns the position as 'line.column'new_position=textwidget.index('{}.{}+ 5 chars - 1 line'.format(line,column))# now new_position is a string, and you may need to parse it back to# separate line and columnTeek:new_position=textwidget.index(line,column).forward(chars=5).back(lines=1)# new_position is now a text position namedtupleTimeoutsIn tkinter,any_widget.after(1000, func)runsfunc()after 1 second, and
theany_widgetcan be any tkinter widget. That's right, you need a widget for
scheduling timeouts. This can be a problem in library code. But what if during
that 1 second of waiting time, you decide that you don't want to run the
timeout after all? You can cancel the timeout, but as usual, teek makes it
easier.Tkinter:widget=get_some_widget_from_somewhere()timeout_id=widget.after(1000,my_function)...# debuggingprint(timeout_id)# prints 'after#0'... very useful, eh??...ifwe_actually_dont_want_to_timeout():widget.after_cancel(timeout_id)print(timeout_id)# still prints 'after#0'Teek:timeout_object=teek.after(1000,my_function)...# debuggingprint(timeout_object)# prints <pending 'my_function' timeout 'after#0'>...ifwe_actually_dont_want_to_timeout():timeout.cancel()print(timeout)# prints <cancelled 'my_function' timeout 'after#0'>Developing teekThis section contains the commands I use when working on teek. If you
use windows, replacepython3withpy.python3 -m pip install --user sphinx pytest pytest-cov flitinstalls
everything you need for developing teek.python3 -m pytestruns tests (they are in thetestssubdirectory). It is
normal to get lots of tiny windows on the screen while running the tests. I
use these pytest options:--skipslowmakes the tests run faster by skipping tests that are
decorated with@pytest.mark.slow.--durations=10prints the list of 10 slowest tests at the end of the
test run. This is a good way to figure out which tests to mark slow.--cov=teekruns the tests under coverage. Runpython3 -m coverage htmland openhtmlcov/index.htmlto view the
results. Coverage results from travis builds go tocoveralls.cd docsfollowed bypy -m sphinx . _buildbuilds documentation locally.
You can view it by openingdocs/_build/index.htmlin your browser.readthedocs builds the docswhen you push to master, but it's best to make
sure that everything's fine first.Sphinx seems to only build parts of the documentation if you change some of
it, but sometimes it doesn't detect your changes. Runcd docsfollowed byrm -r _buildto make it build everything next time.I don't usually lint the files on my system. I push to GitHub (to any
branch), and ifthe travis buildfails, I know I did something badly. If
you want to lint things yourself, find the correct command from.travis.yml.flit publishuploads to PyPI. You can ask me to run this after I have
merged something to master.