API reference

dodo.app

class Dodo

Bases: QApplication

The main Dodo application

There is always one instance of this class, and it contains methods for all of the global (i.e. not view-specific) commands. This includes running global opening/closing panels, opening the help window, and synchronizing mail with the IMAP server.

add_panel(p, focus=True)

Add a panel to the tab view

This method is used by the search, thread, and compose methods to open new panels. In general, this method shouldn’t be called directly from key mappings.

Return type:

None

close_panel(index=None)

Close the panel at index (if provided) or the current panel

If index is not provided, close the current panel. This will only close panels whose keep_open property is False.

Return type:

None

next_panel()

Go to the next panel

Return type:

None

num_panels()

Returns the number of panels (i.e. tabs) currently open

Return type:

int

open_compose(mode='', msg=None)

Open a compose panel

If reply_to is provided, set populate the ‘To’ and ‘In-Reply-To’ headers appropriately, and quote the text in this message.

Parameters:
  • msg (Optional[dict]) – A JSON message referenced in a reply or forward

  • mode (str) – Composition mode. Possible values are ‘’, ‘reply’, ‘replyall’, and ‘forward’

Return type:

None

Open a search panel with the given query

If a panel with this query is already open, switch to it rather than opening another copy.

Return type:

None

open_tags(keep_open=False)

Open tag panel

Return type:

None

open_thread(thread_id)

Open a thread panel with the given thread_id

If a panel with this thread_id is already open, switch to it rather than opening another copy.

Return type:

None

previous_panel()

Go to the previous panel

Return type:

None

prompt_quit()

A ‘soft’ quit function, which gives each open tab the opportunity to prompt the user and possible cancel closing.

Return type:

None

refresh_panels()

Refresh current panel and mark the others as out of date

This method gets called whenever tags have been changed or a new message has been sent. The refresh will happen the next time a panel is switched to.

Return type:

None

search_bar()

Open command bar for searching

Return type:

None

show_help()

Show help window

Return type:

None

sync_mail(quiet=True)

Sync mail with IMAP server

This method runs sync_mail_command, then ‘notmuch new’

Parameters:

quiet (bool) – If this is True, do not give any visual cues that email is being synced. This is less distracting if this is a periodic sync, rather than a manual sync by the user.

Return type:

None

tag_bar(mode='tag')

Open command bar for tagging

Return type:

None

class SyncMailThread(parent=None)

Bases: QThread

A QThread used for syncing local Maildir and notmuch with IMAP

Called by the sync_mail method.

run()

Run sync_mail_command then notmuch new

Return type:

None

main()

Main entry point for Dodo

Return type:

None

dodo.commandbar

class CommandBar(a, label, parent)

Bases: QLineEdit

A command bar that appears on the bottom of the screen when searching or tagging.

accept()

Apply the command typed into the command bar and close

After the command has been applied, this method saves the command to the command history associated with the current mode, then calls close_bar to clear the command and close the command bar.

Return type:

None

close_bar()

Clear the command and close

Call this method by itself to cancel the command and close the bar. Note we use close_bar to avoid a name clash with QWidget.close.

Return type:

None

handleCompletion(text)

Use the choosen tag.

handleTextChanged(text)

Open suggestion dialog if a matching tag is present.

history_next()

Cycle to the next command in the command history

Note a separate history is kept for each mode.

Return type:

None

history_previous()

Cycle to the previous command in the command history

Note a separate history is kept for each mode.

Return type:

None

keyPressEvent(e)

Process keyboard input while the command bar is in focus.

Translate the key event into a string with key_string and check if it is in command_bar_keymap. If it is, fire the associated function. Otherwise, pass the event on to the text box.

Note: Key chords are NOT supported in the command bar.

Return type:

None

open(mode, callback)

Open the command bar and give it focus

This method sets the command_area QWidget (which contains the command bar and its label) to be visible, and sets the command_label to be equal to mode.

Parameters:
  • mode (str) – a string used to set the label next to the command bar and keep a unique command history (e.g. ‘search’ history should be different from ‘tag’ history).

  • callback (Callable[[str], Any]) – a function called with the user input to run the command

Return type:

None

dodo.compose

class ComposePanel(a, mode='', msg=None, parent=None)

Bases: Panel

A panel for composing messages

Parameters:
  • mode (str) – Composition mode. Possible values are ‘’, ‘mailto’, ‘reply’, ‘replyall’, and ‘forward’

  • msg (Optional[dict]) – A JSON message referenced in a reply or forward. If mode != ‘’, this cannot be None.

account_name()

Return the name of the current SMTP account

Return type:

str

attach_file()

Attach a file

Opens a file browser for selecting a file to attach. If a file is selected, add it using the “A:” pseudo-header. This will be translated into a proper attachment when the message is sent.

This command can also use the optional setting file_picker_command to run an external file picker instead of Qt’s built-in one.

Return type:

None

edit()

Edit the email message with an external text editor

The editor is configured via dodo.settings.editor_command.

Return type:

None

email_address()

Return email address that should be used in From: header

Return type:

str

next_account()

Cycle to the next SMTP account in smtp_accounts

Return type:

None

previous_account()

Cycle to the previous SMTP account in smtp_accounts

Return type:

None

refresh()

Refresh the message text

This gets called automatically after the external editor has closed.

Return type:

None

send()

Send the message

Sends asynchronously using SendmailThread. If one or more occurances of the “A:” pseudo-header are detected, these are converted into attachments.

Return type:

None

title()

The title shown on this panel’s tab

Return type:

str

toggle_wrap()

Toggle message wrapping

Tell Dodo to apply hard wrapping to message text for viewing and sending. This maintains an unwrapped copy of the text for editing.

Return type:

None

class EditorThread(panel, parent=None)

Bases: QThread

A QThread used for editing mail with the external editor

Used by the edit method.

run()
Return type:

None

class SendmailThread(panel, parent=None)

Bases: QThread

A QThread used for editing mail with the external editor

Used by the edit method.

run()
Return type:

None

dodo.helpwindow

class HelpWindow(parent=None)

Bases: QWidget

A window showing all keybindings

keyPressEvent(e)

Handle key press

If <escape> is pressed, exit, otherwise pass the keypress on.

Return type:

None

dodo.keymap

command_bar_keymap = {'<down>': ('history next', <function <lambda>>), '<enter>': ('accept', <function <lambda>>), '<escape>': ('close', <function <lambda>>), '<up>': ('history previous', <function <lambda>>), 'C-n': ('history next', <function <lambda>>), 'C-p': ('history previous', <function <lambda>>)}

The keymap active when the command bar is visible

A dictionary from key strings to pairs consisting of a short docstring and a function taking CommandBar as input. Unlike the other keymaps, the command bar keymap doesn’t accept keychords. Also, you should avoid mapping alphanumeric keys to commands, as this will interfere with typing.

compose_keymap = {'<enter>': ('edit message', <function <lambda>>), 'S': ('send', <function <lambda>>), '[': ('previous SMTP account', <function <lambda>>), ']': ('next SMTP account', <function <lambda>>), 'a': ('attach file', <function <lambda>>), 's': ('toggle PGP-sign', <function <lambda>>), 'w': ('toggle word wrap', <function <lambda>>)}

The local keymap for compose panels

A dictionary from key strings to pairs consisting of a short docstring and a function taking ComposePanel as input.

global_keymap = {'/': ('search', <function <lambda>>), '?': ('show help', <function <lambda>>), 'F': ('show flagged', <function <lambda>>), 'I': ('show inbox', <function <lambda>>), 'Q': ('quit', <function <lambda>>), 'T': ('show tags', <function <lambda>>), 'U': ('show unread', <function <lambda>>), 'X': ('close all', <function <lambda>>), '`': ('sync mail', <function <lambda>>), 'c': ('compose', <function <lambda>>), 'h': ('previous panel', <function <lambda>>), 'l': ('next panel', <function <lambda>>), 't m': ('tag marked', <function <lambda>>), 't t': ('tag', <function <lambda>>), 'x': ('close panel', <function <lambda>>)}

The global keymap

A dictionary from key strings to pairs consisting of a short docstring and a function taking Dodo as input. This commands can be superseded by the various local keymaps.

search_keymap = {'<down>': ('next thread', <function <lambda>>), '<enter>': ('open thread', <function <lambda>>), '<space>': ('toggle marked', <function <lambda>>), '<tab>': ('next unread', <function <lambda>>), '<up>': ('previous thread', <function <lambda>>), 'C-d': ('down 20', <function <lambda>>), 'C-u': ('up 20', <function <lambda>>), 'G': ('last thread', <function <lambda>>), 'S-<tab>': ('previous unread', <function <lambda>>), 'a': ('tag -inbox -unread', <function <lambda>>), 'f': ('toggle flagged', <function <lambda>>), 'g g': ('first thread', <function <lambda>>), 'j': ('next thread', <function <lambda>>), 'k': ('previous thread', <function <lambda>>), 'u': ('toggle unread', <function <lambda>>)}

The local keymap for search panels

A dictionary from key strings to pairs consisting of a short docstring and a function taking SearchPanel as input.

tag_keymap = {'<down>': ('next tag', <function <lambda>>), '<enter>': ('search tag', <function <lambda>>), '<up>': ('previous tag', <function <lambda>>), 'C-d': ('down 20', <function <lambda>>), 'C-u': ('up 20', <function <lambda>>), 'G': ('last tag', <function <lambda>>), 'g g': ('first tag', <function <lambda>>), 'j': ('next tag', <function <lambda>>), 'k': ('previous tag', <function <lambda>>)}

The local keymap for the tag panel

A dictionary from key strings to pairs consisting of a short docstring and a function taking TagPanel as input.

thread_keymap = {'-': ('page up', <function <lambda>>), '<space>': ('page down', <function <lambda>>), 'A': ('show attachments in file browser', <function <lambda>>), 'C-d': ('scroll down more', <function <lambda>>), 'C-f': ('forward', <function <lambda>>), 'C-u': ('scroll up more', <function <lambda>>), 'G': ('bottom of message', <function <lambda>>), 'H': ('toggle HTML', <function <lambda>>), 'J': ('next message', <function <lambda>>), 'K': ('previous message', <function <lambda>>), 'R': ('reply', <function <lambda>>), 'f': ('toggle flagged', <function <lambda>>), 'g g': ('top of message', <function <lambda>>), 'j': ('scroll down', <function <lambda>>), 'k': ('scroll up', <function <lambda>>), 'r': ('reply to all', <function <lambda>>), 'u': ('toggle unread', <function <lambda>>)}

The local keymap for thread panels

A dictionary from key strings to pairs consisting of a short docstring and a function taking ThreadPanel as input.

dodo.panel

class Panel(a, keep_open=False, parent=None)

Bases: QWidget

A container widget that can handle key events and be shown on a tab

This is the base class for SearchPanel, ThreadPanel, and ComposePanel, which are the main top-level containers used by Dodo.

Parameters:

keep_open (bool) – If this is True, keep the panel open even when instructed to close. This is used to make sure the “Inbox” SearchPanel always stays open.

before_close()

Called before closing a panel

Cleans up temp dirs and returns True. Overriding methods should call this method after checking they are ready to close.

Return type:

bool

Returns:

True if the panel is ready to close, or False to cancel

keyPressEvent(e)

Passes key events to the appropriate keymap

First a key press (possibly with modifiers) is translated into a string representation using key_string. Then, if that string is part of a keychord, start a timer to try to gather more input.

Once we have the full keychord (or a timeout has occurred), check if the string of the keychord is in the keymap set via set_keymap. If so, fire the associated function. Otherwise, check global_keymap and fire the associated function. If it is not in either, swallow the input and do nothing.

Return type:

None

set_keymap(mp)

Set the local keymap to be the given dictionary.

This needs to be called in the __init__ method of each child class to ensure it handles keychords correctly.

Return type:

None

title()

The title shown on this panel’s tab

Return type:

str

dodo.search

class SearchModel(q)

Bases: QAbstractItemModel

A model containing the results of a search

columnCount(index=<PyQt6.QtCore.QModelIndex object>)

The number of columns

Return type:

int

data(index, role=ItemDataRole.DisplayRole)

Overrides QAbstractItemModel.data to populate a view with search results

Return type:

Any

headerData(section, orientation, role=ItemDataRole.DisplayRole)

Overrides QAbstractItemModel.headerData to populate a view with column names

Return type:

Any

index(row, column, parent=<PyQt6.QtCore.QModelIndex object>)

Construct a QModelIndex for the given row and column

Return type:

QModelIndex

num_threads()

The number of threads returned by the search

Return type:

int

parent(child=None)

Always return an invalid index, since there are no nested indices

Return type:

Any

refresh()

Refresh the model by (re-) running “notmuch search”.

Return type:

None

rowCount(index=<PyQt6.QtCore.QModelIndex object>)

The number of rows

This is essentially an alias for num_threads, but it also returns 0 if an index is given to tell Qt not to add any child items.

Return type:

int

thread_id(index)

Return the notmuch thread id associated with the thread at the given model index

Return type:

Optional[str]

thread_json(index)

Return a JSON object associated with the thread at the given model index

Return type:

Optional[dict]

class SearchPanel(a, q, keep_open=False, parent=None)

Bases: Panel

A panel showing the results of a search

This is used as the main entry point for the GUI, i.e. a search for “tag:inbox”.

first_thread()

Select the first thread in the search

Return type:

None

last_thread()

Select the last thread in the search

Return type:

None

next_thread(unread=False)

Select the next thread in the search

Parameters:

unread (bool) – if True, this will jump to the next unread thread

Return type:

None

open_current_thread()

Open the selected thread

Return type:

None

previous_thread(unread=False)

Select the previous thread in the search

Parameters:

unread (bool) – if True, this will jump to the previous unread thread

Return type:

None

refresh()

Refresh the search listing and restore the selection, if possible.

Return type:

None

tag_thread(tag_expr, mode='tag')

Apply the given tag expression to the selected thread

A tag expression is a string consisting of one more statements of the form “+TAG” or “-TAG” to add or remove TAG, respectively, separated by whitespace.

Return type:

None

title()

Give the query as the tab title

Return type:

str

toggle_thread_tag(tag)

Toggle the given thread tag

Return type:

None

dodo.settings

This module holds settings and sets their default values. The values set here should be overridden by the user in ~/.config/dodo/config.py. This can be done as follows:

import dodo
dodo.settings.email_address = 'First Last <me@domain.com>''
dodo.settings.sent_dir = '~/mail/work/Sent'

The settings email_address and sent_dir are required. Dodo may not work correctly unless you set them properly. The rest of the settings have reasonable defaults, as detailed below.

default_to_html = False

Open messages in HTML mode by default, rather than plaintext

editor_command = "xterm -e vim '{file}'"

Command used to launch external text editor

This is a shell command, which additionally takes the {file} placeholder, which is passed the name of a temp file being edited while composing an email.

email_address = ''

Your email address (REQUIRED)

This is used both to populate the ‘From’ field of emails and to (mostly) avoid CC’ing yourself when replying to all. It can be given as ‘NAME <ADDRESS@DOMAIN>’ format. For just one email address, this can be given as a string. From multiple emails, use a dictionary mapping the account names in smtp_accounts to the associated email addresses.

file_browser_command = "nautilus '{dir}'"

Command used to launch external file browser

This is a shell command, which additionally takes the {dir} placeholder. This command is used when viewing attachments, which first dumps the attachments to a temp directory given by {dir}, then opens that directory in a file browser.

file_picker_command = None

Command used to launch external file picker

This is an optional shell command, which additionally takes the {tempfile} placeholder. This command is used when picking files to attach to an email. The command should write out the chosen files to {tempfile}, which will then be read and deleted, if it exists.

By default, this is set to None, in which case the built-in file picker will be used.

gnupg_home = None

Directory containg GnuPG keys

If set to None, GnuPG will use whatever directory is the default (consult the GnuPG documentation for more information on what this might be).

gnupg_keyid = None

The id of the key to be used for GnuPG-signing mail messages.

If set to the id of a valid GnuPG private signing key, sent messages will be cryptographically signed according to rfc3156 using the GnuPG sotware, which should be installed and configured. Requires python-gnupg (https://pypi.org/project/python-gnupg/)

hide_tags = ['unread', 'sent']

Tags to hide in search panel

html_block_remote_requests = True

Block remote requests for HTML messages

HTML messages, especially from dodgy senders, can display remote content or ‘call home’ from embedded image tags or iframes. If set to True, Dodo will not allow these requests.

Display a confirmation dialog before opening a link in browser

If this is True, Dodo will display a confirmation dialog showing the actual URL that the web browser will request before opening. This is an extra measure against phishing or emails opening your web browser without your permission.

init_queries = ['tag:inbox']

List of non closable queries open at startup

You can save query with notmuch config set query:inbox “tag:inbox and not tag:trash” and use query:inbox as a search term.

message_css = '\npre {{\n  font-family: {message_font};\n  font-size: {message_font_size}pt;\n}}\n\npre .quoted {{\n  color: {fg_dim};\n}}\n\npre .headername {{\n  color: {fg_bright};\n  font-weight: bold;\n}}\n\npre .headertext {{\n  color: {fg_bright};\n}}\n\nbody {{\n  background-color: {bg};\n  color: {fg};\n}}\n\n::-webkit-scrollbar {{\n  background: {bg};\n}}\n\n::-webkit-scrollbar-thumb {{\n  background: {bg_button};\n}}\n\n::selection {{\n  color: {bg};\n  background: {fg};\n}}\n\na {{\n  color: {fg_bright};\n}}\n'

CSS used in view and compose window

Placeholders may be included in curly brackets for any color named in the current theme, as well as {message_font} and {message_font_size}. Literal curly braces should be doubled, i.e. ‘{’ should be ‘{{’ and ‘}’ should be ‘}}’.

message_font = 'DejaVu Sans Mono'

The font used for plaintext messages

message_font_size = 12

The font size used for plaintext messages

remove_temp_dirs = 'ask'

Set whether to remove temporary directories when closing a panel

Thread panels create temporary directories to open attachments. These can be cleaned up automatically when a panel (or Dodo) is closed. Possible values are: ‘always’, ‘never’, or ‘ask’.

search_font = 'DejaVu Sans Mono'

The font used for search output and various other list-boxes

search_font_size = 13

The font size used for search output and various other list-boxes

search_view_padding = 1

A bit of spacing around each line in the search panel

send_mail_command = 'msmtp -a "{account}" -t'

Command used to send mail via SMTP

This is a shell command that expects a (sendmail-compatible) email message to be written to STDIN. Note that it should read the destination from the From: header of the message and not a command-line argument. Use the {account} placeholder to read the currently selected account.

sent_dir = ''

Where to store sent messages (REQUIRED)

This will usually be a subdirectory of the Maildir sync’ed with sync_mail_command. This setting can be given either as a string to use one global sent directory, or as a dictionary mapping account names in smtp_accounts to their own sent dirs.

smtp_accounts = ['default']

A list of SMTP account names recognised by send_mail_command

This setting allows switching SMTP accounts in the Compose panel. The first account in the list is selected by default.

sync_mail_command = 'offlineimap'

Command used to sync IMAP with local Maildir

sync_mail_interval = 300

Interval to run sync_mail_command automatically, in seconds

Set this to -1 to disable automatic syncing.

tag_font = 'DejaVu Sans Mono'

The font used for tags and tag icons

tag_font_size = 13

The font size used for tags and tag icons

tag_icons = {'attachment': '\uf565', 'flagged': '\uf73a', 'inbox': '\uf01c', 'marked': '\uf111', 'replied': '\uf4a8', 'sent': '>', 'signed': '\uf040', 'unread': '\uf0e0'}

Tag icons

This is a dictionary of substitutions used to abbreviate common tag names as unicode icons in the search and thread panels.

theme = {'bg': '#2e3440', 'bg_alt': '#3b4252', 'bg_button': '#4c566a', 'bg_highlight': '#a3be8c', 'fg': '#d8dee9', 'fg_bad': '#bf616a', 'fg_bright': '#b48ead', 'fg_button': '#eceff4', 'fg_date': '#4c566a', 'fg_dim': '#4c566a', 'fg_from': '#5e81ac', 'fg_good': '#a3be8c', 'fg_highlight': '#2e3440', 'fg_link': '#81a1c1', 'fg_subject': '#d8dee9', 'fg_subject_flagged': '#ebcb8b', 'fg_subject_unread': '#b48ead', 'fg_tags': '#81a1c1'}

The GUI theme

A theme is a dictionary mapping a dozen or so named colors to HEX values. Several themes are defined in dodo.themes, based on the popular Nord and Solarized color palettes.

web_browser_command = ''

Web browser to use when clicking links in emails

This should be a single command which expects a URL as its first argument. If this is an empty string, Dodo will attempt to use the default web browser supplied by the desktop environment, if it exists.

wrap_column = 78

Wrap text to this column when composing emails

wrap_message = True

Hard-wrap message text by default

You may wish to disable this if you don’t want hard wraps in your email messages or your text editor does hard wrapping already.

dodo.themes

apply_theme(theme)

“Apply the given theme to GUI components

This is called when Dodo is initialised.

Return type:

None

catppuccin_macchiato = {'bg': '#24273a', 'bg_alt': '#181926', 'bg_button': '#363a4f', 'bg_highlight': '#8aadf4', 'fg': '#cad3f5', 'fg_bad': '#ed8796', 'fg_bright': '#b7bdf8', 'fg_button': '#f4dbd6', 'fg_date': '#f0c6c6', 'fg_dim': '#8087a2', 'fg_from': '#8aadf4', 'fg_good': '#a6da95', 'fg_highlight': '#181926', 'fg_link': '#8aadf4', 'fg_subject': '#cad3f5', 'fg_subject_flagged': '#eed49f', 'fg_subject_unread': '#c6a0f6', 'fg_tags': '#f5a97f'}

Theme based on the Catppuchin palette (macchiatto version).

nord = {'bg': '#2e3440', 'bg_alt': '#3b4252', 'bg_button': '#4c566a', 'bg_highlight': '#a3be8c', 'fg': '#d8dee9', 'fg_bad': '#bf616a', 'fg_bright': '#b48ead', 'fg_button': '#eceff4', 'fg_date': '#4c566a', 'fg_dim': '#4c566a', 'fg_from': '#5e81ac', 'fg_good': '#a3be8c', 'fg_highlight': '#2e3440', 'fg_link': '#81a1c1', 'fg_subject': '#d8dee9', 'fg_subject_flagged': '#ebcb8b', 'fg_subject_unread': '#b48ead', 'fg_tags': '#81a1c1'}

Theme based on the Nord palette

solarized_dark = {'bg': '#073642', 'bg_alt': '#002b36', 'bg_button': '#002b36', 'bg_highlight': '#eee8d5', 'fg': '#93a1a1', 'fg_bad': '#dc322f', 'fg_bright': '#6c71c4', 'fg_button': '#586e75', 'fg_date': '#2aa198', 'fg_dim': '#586e75', 'fg_from': '#268bd2', 'fg_good': '#859900', 'fg_highlight': '#586e75', 'fg_link': '#6c71c4', 'fg_subject': '#839496', 'fg_subject_flagged': '#6c71c4', 'fg_subject_unread': '#eee8d5', 'fg_tags': '#6c71c4'}

Theme based on the Solarized palette (dark background).

solarized_light = {'bg': '#fdf6e3', 'bg_alt': '#fdf6e3', 'bg_button': '#fdf6e3', 'bg_highlight': '#073642', 'fg': '#586e75', 'fg_bad': '#dc322f', 'fg_bright': '#6c71c4', 'fg_button': '#93a1a1', 'fg_date': '#2aa198', 'fg_dim': '#93a1a1', 'fg_from': '#268bd2', 'fg_good': '#859900', 'fg_highlight': '#93a1a1', 'fg_link': '#6c71c4', 'fg_subject': '#839496', 'fg_subject_flagged': '#6c71c4', 'fg_subject_unread': '#073642', 'fg_tags': '#6c71c4'}

Theme based on the Solarized palette (light background).

dodo.thread

class EmbeddedImageHandler(parent=None)

Bases: QWebEngineUrlSchemeHandler

requestStarted(request)
Return type:

None

class MessageHandler(parent=None)

Bases: QWebEngineUrlSchemeHandler

requestStarted(request)
Return type:

None

class MessagePage(a, profile, parent=None)

Bases: QWebEnginePage

acceptNavigationRequest(url, ty, isMainFrame)
Return type:

bool

class ThreadModel(thread_id)

Bases: QAbstractItemModel

A model containing a thread, its messages, and some metadata

This extends QAbstractItemModel to enable a tree view to give a summary of the messages, but also contains more data that the tree view doesn’t care about (e.g. message bodies). Since this comes from calling “notmuch show –format=json”, it contains information about attachments (e.g. filename), but not attachments themselves.

Parameters:

thread_id (str) – the unique thread identifier used by notmuch

columnCount(index=<PyQt6.QtCore.QModelIndex object>)

Constant = 1

Return type:

int

data(index, role=ItemDataRole.DisplayRole)

Overrides QAbstractItemModel.data to populate a list view with short descriptions of messages in the thread.

Currently, this just returns the message sender and makes it bold if the message is unread. Adding an emoji to show attachments would be good.

Return type:

Any

default_message()

Return the index of either the oldest unread message or the last message in the thread.

Return type:

int

index(row, column, parent=<PyQt6.QtCore.QModelIndex object>)

Construct a QModelIndex for the given row and (irrelevant) column

Return type:

QModelIndex

message_at(i)

A JSON object describing the i-th message in the (flattened) thread

Return type:

dict

num_messages()

The number of messages in the thread

Return type:

int

parent(child=None)

Always return an invalid index, since there are no nested indices

Return type:

Any

refresh()

Refresh the model by calling “notmuch show”.

Return type:

None

rowCount(index=<PyQt6.QtCore.QModelIndex object>)

The number of rows

This is essentially an alias for num_messages, but it also returns 0 if an index is given to tell Qt not to add any child items.

Return type:

int

class ThreadPanel(a, thread_id, parent=None)

Bases: Panel

A panel showing an email thread

This is the panel used for email viewing.

Parameters:
  • app – the unique instance of the Dodo app class

  • thread_id (str) – the unique ID notmuch uses to identify this thread

forward()

Open a ComposePanel populated with a forwarded message

Return type:

None

layout_panel()

Method for laying out various components in the ThreadPanel

next_message()

Show the next message in the thread

Return type:

None

open_attachments()

Write attachments out into temp directory and open with settings.file_browser_command

Currently, this exports a new copy of the attachments every time it is called. Maybe it should do something smarter?

Return type:

None

previous_message()

Show the previous message in the thread

Return type:

None

refresh()

Refresh the panel using the output of “notmuch show”

Note the view of the message body is not refreshed, as this would pop the user back to the top of the message every time it happens. To refresh the current message body, use show_message wihtout any arguments.

Return type:

None

reply(to_all=True)

Open a ComposePanel populated with a reply

This uses the current message as the message to reply to. This should probably do something smarter if the current message is from the user (e.g. reply to the previous one instead).

Parameters:

to_all (bool) – if True, do a reply to all instead (see ~dodo.compose.ComposePanel)

Return type:

None

scroll_message(lines=None, pages=None, pos=None)

Scroll the message body

This operates in 3 different modes, depending on which arguments are given. Precisely one of the three arguments lines, pages, and pos should be provided.

Parameters:
  • lines (Optional[int]) – scroll up/down the given number of 20-pixel increments. Negative numbers scroll up.

  • pages (Union[float, int, None]) – scroll up/down the given number of pages. Negative numbers scroll up.

  • pos (Optional[str]) – scroll to the given position (possible values are ‘top’ and ‘bottom’)

Return type:

None

show_message(i=-1)

Show a message

If an index is provided, switch the current message to that index, otherwise refresh the view of the current message.

Return type:

None

tag_message(tag_expr)

Apply the given tag expression to the current message

A tag expression is a string consisting of one more statements of the form “+TAG” or “-TAG” to add or remove TAG, respectively, separated by whitespace.

Return type:

None

title()

The tab title

The title is given as the (shortened) subject of the currently visible message.

Return type:

str

toggle_html()

Toggle between HTML and plain text message view

Return type:

None

toggle_message_tag(tag)

Toggle the given tag on the current message

Return type:

None

flat_thread(d)

Return the thread as a flattened list of messages, sorted by date.

Return type:

List[dict]

short_string(m)

Return a short string describing the provided message

Currently, this just returns the contents of the “From” header, but something like a first name and short/relative date might be more useful.

Parameters:

m (dict) – A JSON message object

Return type:

str

dodo.util

add_header_line(s, h)

Add the given string to the headers, i.e. before the first blank line, in the provided string.

Return type:

str

body_html(m)

Get the body HTML of a message

Search a message recursively for the first part with content-type equal to “text/html” and return it.

Parameters:

m (dict) – a JSON message

Return type:

str

body_text(m)

Get the body text of a message

Search a message recursively for the first part with content-type equal to “text/plain” and return it.

Parameters:

m (dict) – a JSON message

Return type:

str

clean_html2html(s)

Sanitize the given HTML string

This cleans the input string using Cleaner with the default settings. Set the global util.html2html to this function to enable.

Parameters:

s (str) – an HTML input string

Return type:

str

colorize_text(s, has_headers=False)

Add some colors to HTML-escaped plaintext, for use inside <pre> tag

Return type:

str

decode_header(s)

Decode any charset-encoded parts of an email header

Return type:

str

email_is_me(e)

Check whether the provided email is me

This compares settings.email_address with the provided email, after calling strip_email_address on both. This method is used e.g. by dodo.compose.Compose to filter out the user’s own email when forming a “reply-to-all” message.

Return type:

bool

email_smtp_account_index(e)

Index in settings.smtp_accounts of account having the provided email address

This method is used e.g. by dodo.compose.Compose to autmatically select the account to be used when replying to a mail. It returns the index of first matching account or None if provided email does not match any smtp account.

Return type:

Optional[int]

find_content(m, content_type)

Return a flat list consisting of the ‘content’ field of each message part with the given content-type.

Return type:

List[str]

html2html(s)

Function used to process HTML messages

This is the identity by default, but can be set to another function to do HTML sanitization, (de)formatting, etc.

html2text(s)

Function used to convert HTML to plain text

This is set to w3m_html2text by default, but can be changed by the user in “config.py”.

Return type:

str

key_string(e)

Convert a Qt keycode plus modifiers into a human readable/writable string

Parameters:

e (QKeyEvent) – a QKeyEvent

Return type:

str

Returns:

a string representing e.key() and its modifiers

linkify(s)

Link URLs and email addresses

Parameters:

s (str) – a plaintext input string

Return type:

str

Returns:

HTML with URLs and emails linked

make_message_css()

Fill placeholders in settings.message_css using the current theme and font settings.

Return type:

str

message_parts(m)

Iterate over JSON message parts recursively, in depth-first order

This is method roughly emulates the behavior of walk, but for JSON representations of an email message rather than Message objects.

Note that if parts are nested, their data will be returned multiple times, first as a sub-object of their parent, then as the part itself.

Parameters:

m (dict) – a JSON message

Return type:

Iterator[dict]

Returns:

an iterator which returns each JSON-subobject corresponding to a message part.

quote_body_text(m)

Return the body text of the message, with ‘>’ prepended to each line

Return type:

str

replace_header(s, h, new_value)

Replace a single header without doing full message parsing

Note this ONLY works for short (i.e. unwrapped) headers.

Return type:

str

separate_headers(s)

Split a message into its header part and body part

Return type:

Tuple[str, str]

simple_escape(s)

Provide (limited) HTML escaping

This function only escapes &, <, and >.

Return type:

str

strip_email_address(e)

Strip the display name, leaving just the email address

E.g. “First Last <me@domain.com>” -> “me@domain.com

Return type:

str

w3m_html2text(s)

Convert HTML to plain text using “w3m -dump”

Parameters:

s (str) – an HTML input string

Return type:

str

Returns:

plain text representation of the HTML

wrap_message(s)

Hard wrap message body using wrap_column

Wrap the body part of the message. Headers and quoted text are not affected.

Return type:

str

write_attachments(m)

Write attachments out into temp directory and open with settings.file_browser_command

Currently, this exports a new copy of the attachments every time it is called. Maybe it should do something smarter?

Parameters:

m (dict) – message JSON

Return type:

Tuple[str, List[str]]

Returns:

Return a tuple consisting of the temp dir and a list of files. If no attachments, returns an empty string and empty list.