mirror of
https://github.com/akissinger/dodo.git
synced 2024-10-04 06:27:25 +03:00
added ability to create custom command bars
This commit is contained in:
parent
2da86d221c
commit
779451947c
31
dodo/app.py
31
dodo/app.py
@ -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."""
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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>'
|
||||
|
@ -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 = {
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
@ -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',
|
||||
msg=self.model.message_at(self.current_message))
|
||||
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`
|
||||
|
Loading…
Reference in New Issue
Block a user