added ability to create custom command bars

This commit is contained in:
Aleks Kissinger 2022-01-29 13:28:48 +00:00
parent 2da86d221c
commit 779451947c
6 changed files with 45 additions and 34 deletions

View File

@ -99,7 +99,7 @@ class Dodo(QApplication):
timer.start(settings.sync_mail_interval * 1000)
# open inbox and make un-closeable
self.search('tag:inbox', keep_open=True)
self.open_search('tag:inbox', keep_open=True)
def show_help(self) -> None:
"""Show help window"""
@ -150,7 +150,7 @@ class Dodo(QApplication):
if w.before_close():
self.tabs.removeTab(index)
def search(self, query: str, keep_open: bool=False) -> None:
def open_search(self, query: str, keep_open: bool=False) -> None:
"""Open a search panel with the given query
If a panel with this query is already open, switch to it rather than
@ -180,7 +180,7 @@ class Dodo(QApplication):
p = thread.ThreadPanel(self, thread_id)
self.add_panel(p)
def compose(self, mode: str='', msg: Optional[dict]=None) -> None:
def open_compose(self, mode: str='', msg: Optional[dict]=None) -> None:
"""Open a compose panel
If reply_to is provided, set populate the 'To' and 'In-Reply-To' headers
@ -194,6 +194,20 @@ class Dodo(QApplication):
p = compose.ComposePanel(self, mode, msg)
self.add_panel(p)
def search_bar(self) -> None:
"""Open command bar for searching"""
self.command_bar.open('search', callback=self.open_search)
def tag_bar(self) -> None:
"""Open command bar for tagging"""
def callback(tag_expr: str) -> None:
w = self.tabs.currentWidget()
if w:
if isinstance(w, search.SearchPanel): w.tag_thread(tag_expr)
elif isinstance(w, thread.ThreadPanel): w.tag_message(tag_expr)
w.refresh()
self.command_bar.open('tag', callback)
def sync_mail(self, quiet: bool=True) -> None:
"""Sync mail with IMAP server
@ -206,9 +220,7 @@ class Dodo(QApplication):
t = SyncMailThread(parent=self)
def done() -> None:
self.invalidate_panels()
w = self.tabs.currentWidget()
if w: w.refresh()
self.refresh_panels()
if not quiet:
title = self.main_window.windowTitle()
self.main_window.setWindowTitle(title.replace(' [syncing]', ''))
@ -226,8 +238,8 @@ class Dodo(QApplication):
return self.tabs.count()
def invalidate_panels(self) -> None:
"""Mark all panels as out of date
def refresh_panels(self) -> None:
"""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."""
@ -237,6 +249,9 @@ class Dodo(QApplication):
if isinstance(w, panel.Panel):
w.dirty = True
w = self.tabs.currentWidget()
if w: w.refresh()
def prompt_quit(self) -> None:
"""A 'soft' quit function, which gives each open tab the opportunity to prompt
the user and possible cancel closing."""

View File

@ -17,7 +17,7 @@
# along with Dodo. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import Dict, List, Tuple
from typing import Dict, List, Tuple, Optional, Callable, Any
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QKeyEvent
@ -36,9 +36,10 @@ class CommandBar(QLineEdit):
self.app = a
self.label = label
self.mode = ''
self.history: Dict[str, Tuple[int, List[str]]] = { 'search': (0, []), 'tag': (0, []) }
self.history: Dict[str, Tuple[int, List[str]]] = {}
self.callback: Optional[Callable[[app.Dodo, str], Any]] = None
def open(self, mode: str) -> None:
def open(self, mode: str, callback: Callable[[app.Dodo, str], Any]) -> None:
"""Open the command bar and give it focus
This method sets the `command_area` QWidget (which contains the command bar and
@ -48,6 +49,7 @@ class CommandBar(QLineEdit):
its behaviour. Recognized values are "search" and "tag"."""
self.mode = mode
self.callback = callback
self.label.setText(mode)
self.parent().setVisible(True)
self.setFocus()
@ -74,19 +76,15 @@ class CommandBar(QLineEdit):
history associated with the current mode, then calls :func:`close_bar` to clear
the command and close the command bar."""
if self.mode == 'search':
self.app.search(self.text())
elif self.mode == 'tag':
w = self.app.tabs.currentWidget()
if w:
if isinstance(w, search.SearchPanel): w.tag_thread(self.text())
elif isinstance(w, thread.ThreadPanel): w.tag_message(self.text())
w.refresh()
if self.callback:
self.callback(self.text())
if self.mode in self.history:
pos, h = self.history[self.mode]
h.append(self.text())
self.history[self.mode] = (pos + 1, h)
else:
self.history[self.mode] = (1, [self.text()])
self.close_bar()

View File

@ -294,7 +294,7 @@ class SendmailThread(QThread):
if ((self.panel.mode == 'reply' or self.panel.mode == 'replyall') and
self.panel.msg and 'id' in self.panel.msg):
subprocess.run(['notmuch', 'tag', '+replied', '--', 'id:' + self.panel.msg['id']])
self.panel.app.invalidate_panels()
self.panel.app.refresh_panels()
self.panel.status = f'<i style="color:{settings.theme["fg_good"]}">sent</i>'
else:
self.panel.status = f'<i style="color:{settings.theme["fg_bad"]}">error</i>'

View File

@ -24,12 +24,12 @@ global_keymap = {
'h': ('previous panel', lambda a: a.previous_panel()),
'x': ('close panel', lambda a: a.close_panel()),
'X': ('close all', lambda a: [a.close_panel(i) for i in reversed(range(a.num_panels()))]),
'c': ('compose', lambda a: a.compose()),
'I': ('show inbox', lambda a: a.search('tag:inbox')),
'U': ('show unread', lambda a: a.search('tag:inbox and tag:unread')),
'F': ('show flagged', lambda a: a.search('tag:flagged')),
'/': ('search', lambda a: a.command_bar.open('search')),
't': ('tag', lambda a: a.command_bar.open('tag')),
'c': ('compose', lambda a: a.open_compose()),
'I': ('show inbox', lambda a: a.open_search('tag:inbox')),
'U': ('show unread', lambda a: a.open_search('tag:inbox and tag:unread')),
'F': ('show flagged', lambda a: a.open_search('tag:flagged')),
'/': ('search', lambda a: a.search_bar()),
't': ('tag', lambda a: a.tag_bar()),
}
search_keymap = {

View File

@ -243,8 +243,7 @@ class SearchPanel(panel.Panel):
if thread_id:
subprocess.run(['notmuch', 'tag'] + tag_expr.split() + ['--', 'thread:' + thread_id])
self.app.invalidate_panels()
self.refresh()
self.app.refresh_panels()

View File

@ -80,7 +80,7 @@ class MessagePage(QWebEnginePage):
if url.scheme() == 'mailto':
query = QUrlQuery(url)
msg = {'headers':{'To': url.path(), 'Subject': query.queryItemValue('subject')}}
self.app.compose(mode='mailto', msg=msg)
self.app.open_compose(mode='mailto', msg=msg)
else:
if (not settings.html_confirm_open_links or
QMessageBox.question(None, 'Open link',
@ -482,8 +482,7 @@ class ThreadPanel(panel.Panel):
tag_expr = '+' + tag_expr
r = subprocess.run(['notmuch', 'tag'] + tag_expr.split() + ['--', 'id:' + m['id']],
stdout=subprocess.PIPE)
self.app.invalidate_panels()
self.refresh()
self.app.refresh_panels()
def toggle_html(self) -> None:
"""Toggle between HTML and plain text message view"""
@ -500,14 +499,14 @@ class ThreadPanel(panel.Panel):
:param to_all: if True, do a reply to all instead (see `~dodo.compose.ComposePanel`)
"""
self.app.compose(mode='replyall' if to_all else 'reply',
self.app.open_compose(mode='replyall' if to_all else 'reply',
msg=self.model.message_at(self.current_message))
def forward(self) -> None:
"""Open a :class:`~dodo.compose.ComposePanel` populated with a forwarded message
"""
self.app.compose(mode='forward', msg=self.model.message_at(self.current_message))
self.app.open_compose(mode='forward', msg=self.model.message_at(self.current_message))
def open_attachments(self) -> None:
"""Write attachments out into temp directory and open with `settings.file_browser_command`