Org-Mode as simple RAD GUI toolkit
It took me way to long since the last article teasing this one, but
today I want to finally describe how I am using Emacs and Org-Mode as
a simple RAD (rapid application development) GUI toolkit.
For your convenience, you can find my old article here:
Let's start with my very abstract requirements: I want to use the
power of plain text to manage some data. Plain text is no silver
bullet, but it has some cool qualities: It is well-supported by lots
of applications (be it powerful ones like Emacs or simple ones like
your favorite shell commands, e.g. grep), it is searchable (grep,
again) and it is efficient to compress and easy to diff (which is good
for storing it in version control systems like Git).
But at the same time, I want to hide some complexities behind simpler
UI elements. Even for me as a terminal zealot, there is a mental toll
for having to remember all these shell commands with their
arguments. And I tend to forget things I did not need in a while: What
once was absolutely natural to me might be gone when I need it the
next time. A good GUI has one large advantage over a shell command:
You can quickly discover how to do something and only need to remember
where to find the correct widget to do so. The more trivial your GUI
is, the more this is true.
A concrete example
I gave two examples in my previous example: Managing and executing
unit tests and managing and watching videos. I will go into more
detail for the second example here, but the fundamentals should be
simple to apply for any other use case.
My concrete requirements are these: I often find videos on Youtube
that I want to watch at some later time. But I do not like watching
them in a browser, but prefer my dear VLC player. Therefore, I want to
somehow remember the videos and watch them via their URLs. I need a
way to store these URLs and I want to just click on a button to start
VLC with the given video. I also want to know the title of each video
- it would be bad for my user experience if all I had were the
URLs. But I also want to insert as few meta information manually as
possible.
* Designing the Org-Mode document
At its most basic form, an Org-Mode document is just a text document
that is structured via (cascaded) headlines - Org-Mode is an outliner
above all. I am using a separate headline for each category of
videos. For example, I separate technical videos from gaming videos.
The contents below each headline can be anything: Unstructured text,
links, lists, tables, subsections with own headlines, ... We will be
making good use of tables and links here.
My requirements lead to tables because tables can have formulas. This
makes it possible to only fill in part of a row and automatically set
the other table cells. But this is Emacs: I could also write custom
commands that could generate any structure I want. I also could make
use of Org-Mode capture templates, which are supposed to quickly make
a note of something without having to much of a context switch, which
also matches my requirements. They might be a better fit for your
requirements - here, I will use tables.
The table will have at least four columns: I want to input the URL in
one column, have a link to watch it in the next column and see the
title in the third column. I also want to mark the video as watched -
I will do this manually in the fourth column. If I want to add further
information, it would be trivial to add more columns or add a footnote
to some column, which will jump to a detail section. Let's keep it
simple here.
The links in the second column will be generated via a formula and
will execute a small bit of Emacs Lisp when clicked. I will get to
that later.
The Org-Mode document
Here is a simple example of my document:
* Video List | URL | | Title | Finished | |---------------------------------------------+---+-------+----------| | https://www.youtube.com/watch?v=TSyGryuMh-U | | | | |---------------------------------------------+---+-------+----------| #+TBLFM: $2='(format "[[elisp:(vlc \"%s\")][PLAY]]" $1)::$3='(if (is-youtube-url $1) (get-youtube-title $1) "")
This shows the static content of my document, which I had to create
once. At this point of time, I have pasted a single URL into the
table, but I have not evaluated the formula (via C-*) yet.
When I evaluate the formula, it fills in the second and third column
as described: The second column creates a link, which uses square
brackets in Org-Mode. The link is just labeled as "PLAY" and calls the
"vlc" function with the value from the first column as a parameter.
The third column checks whether the first column contains a valid
Youtube URL (via the "is-youtube-url" function). If it does so, it
extracts the title via the "get-youtube-title" function. Otherwise, it
just returns an empty string. To make good use of this document, I
will have to define these three functions and store them in my Emacs
configuration.
The Elisp functions
(defun get-youtube-title (url) (let ((default-directory "~")) (let* ((json (json-parse-string (shell-command-to-string (format "PYTHONWARNINGS=ignore ~/.local/bin/yt-dlp -j --no-warnings \"%s\"" url)))) (title (gethash "title" json))) (replace-regexp-in-string "|" "-" title)))) (defun is-youtube-url (url) (string-match-p "^https://www\\.youtube\\.com/watch\\?v=.*$" url)) (defun vlc (url) (let ((default-directory "~") (vlc-cmd "vlc")) (async-shell-command (format "%s \"%s\"" vlc-cmd url))))
You can find some interesting details in my otherwise simple
definitions of these three functions. The first one is that I am using
yt-dlp to get all the video information as a JSON structure. This can
be parsed by built-in Emacs functions. I also silence all warnings for
this call, which disturbs my parsing otherwise, leaving me with
invalid JSON.
Another detail is that I might store this document on a different
system then the one I am watching my videos on. Emacs is good at
opening files via SSH (using Tramp mode), so I do not care much about
the locality of my files. I also open my document via a custom command
and therefore do not need to type in the network path each time - it
always works the same wherever I am. But when I execute yt-dlp or vlc,
I want to execute my application locally (especially vlc!). That is
the reason for me setting the default-directory temporarily to my home
directory before executing the program - I am forcing a return to my
local system here.
Closing
I have shown how I use Org-Mode to create a simple application with
clickable buttons and automatically determined data. I am using small
documents like these quite often and I hope I inspired you (or at
least the Emacs users here) to consider using Org-Mode for similar
tasks, which can help reduce your own mental load while keeping the
advantages of plain text documents.