Add a bookmarkstab (fixes #2004)
now we can edit stuff, save or cancel those modifications, and change the chose storage easily
This commit is contained in:
parent
2da4474d6a
commit
9141e0c4d3
7 changed files with 444 additions and 2 deletions
|
@ -501,12 +501,15 @@ def _add_wildcard_bookmarks(self, method):
|
|||
def command_bookmarks(self):
|
||||
"""/bookmarks"""
|
||||
tab = self.get_tab_by_name('Bookmarks', tabs.BookmarksTab)
|
||||
old_tab = self.current_tab()
|
||||
if tab:
|
||||
self.current_tab_nb = tab.nb
|
||||
else:
|
||||
tab = tabs.BookmarksTab(self.bookmarks)
|
||||
self.tabs.append(tab)
|
||||
self.current_tab_nb = tab.nb
|
||||
old_tab.on_lose_focus()
|
||||
tab.on_gain_focus()
|
||||
self.refresh_window()
|
||||
|
||||
@command_args_parser.quoted(0, 1)
|
||||
|
|
|
@ -10,3 +10,4 @@ from . listtab import ListTab
|
|||
from . muclisttab import MucListTab
|
||||
from . adhoc_commands_list import AdhocCommandsListTab
|
||||
from . data_forms import DataFormsTab
|
||||
from . bookmarkstab import BookmarksTab
|
||||
|
|
147
src/tabs/bookmarkstab.py
Normal file
147
src/tabs/bookmarkstab.py
Normal file
|
@ -0,0 +1,147 @@
|
|||
"""
|
||||
Defines the data-forms Tab
|
||||
"""
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import windows
|
||||
from bookmarks import Bookmark, BookmarkList, stanza_storage
|
||||
from tabs import Tab
|
||||
from common import safeJID
|
||||
|
||||
from gettext import gettext as _
|
||||
|
||||
|
||||
class BookmarksTab(Tab):
|
||||
"""
|
||||
A tab displaying lines of bookmarks, each bookmark having
|
||||
a 4 widgets to set the jid/password/autojoin/storage method
|
||||
"""
|
||||
plugin_commands = {}
|
||||
def __init__(self, bookmarks: BookmarkList):
|
||||
Tab.__init__(self)
|
||||
self.name = "Bookmarks"
|
||||
self.bookmarks = bookmarks
|
||||
self.new_bookmarks = []
|
||||
self.removed_bookmarks = []
|
||||
self.header_win = windows.ColumnHeaderWin(('room@server/nickname',
|
||||
'password',
|
||||
'autojoin',
|
||||
'storage'))
|
||||
self.bookmarks_win = windows.BookmarksWin(self.bookmarks,
|
||||
self.height-4,
|
||||
self.width, 1, 0)
|
||||
self.help_win = windows.HelpText(_('Ctrl+Y: save, Ctrl+G: cancel, '
|
||||
'↑↓: change lines, tab: change '
|
||||
'column, M-a: add bookmark, C-k'
|
||||
': delete bookmark'))
|
||||
self.info_header = windows.BookmarksInfoWin()
|
||||
self.key_func['KEY_UP'] = self.bookmarks_win.go_to_previous_line_input
|
||||
self.key_func['KEY_DOWN'] = self.bookmarks_win.go_to_next_line_input
|
||||
self.key_func['^I'] = self.bookmarks_win.go_to_next_horizontal_input
|
||||
self.key_func['^G'] = self.on_cancel
|
||||
self.key_func['^Y'] = self.on_save
|
||||
self.key_func['M-a'] = self.add_bookmark
|
||||
self.key_func['^K'] = self.del_bookmark
|
||||
self.resize()
|
||||
self.update_commands()
|
||||
|
||||
def add_bookmark(self):
|
||||
new_bookmark = Bookmark(safeJID('room@example.tld/nick'), method='local')
|
||||
self.new_bookmarks.append(new_bookmark)
|
||||
self.bookmarks_win.add_bookmark(new_bookmark)
|
||||
|
||||
def del_bookmark(self):
|
||||
current = self.bookmarks_win.del_current_bookmark()
|
||||
if current in self.new_bookmarks:
|
||||
self.new_bookmarks.remove(current)
|
||||
else:
|
||||
self.removed_bookmarks.append(current)
|
||||
|
||||
def on_cancel(self):
|
||||
self.core.close_tab()
|
||||
return True
|
||||
|
||||
def on_save(self):
|
||||
self.bookmarks_win.save()
|
||||
if find_duplicates(self.new_bookmarks):
|
||||
self.core.information(_('Duplicate bookmarks in list (saving aborted)'), 'Error')
|
||||
return
|
||||
for bm in self.new_bookmarks:
|
||||
if safeJID(bm.jid):
|
||||
if not self.bookmarks[bm.jid]:
|
||||
self.bookmarks.append(bm)
|
||||
else:
|
||||
self.core.information(_('Invalid JID for bookmark: %s/%s') % (bm.jid, bm.nick), 'Error')
|
||||
return
|
||||
|
||||
for bm in self.removed_bookmarks:
|
||||
if bm in self.bookmarks:
|
||||
self.bookmarks.remove(bm)
|
||||
|
||||
def send_cb(success):
|
||||
if success:
|
||||
self.core.information(_('Bookmarks saved.'), 'Info')
|
||||
else:
|
||||
self.core.information(_('Remote bookmarks not saved.'), 'Error')
|
||||
log.debug('alerte %s', str(stanza_storage(self.bookmarks.bookmarks)))
|
||||
self.bookmarks.save(self.core.xmpp, callback=send_cb)
|
||||
self.core.close_tab()
|
||||
return True
|
||||
|
||||
def on_input(self, key, raw=False):
|
||||
if key in self.key_func:
|
||||
res = self.key_func[key]()
|
||||
if res:
|
||||
return res
|
||||
self.bookmarks_win.refresh_current_input()
|
||||
else:
|
||||
self.bookmarks_win.on_input(key)
|
||||
|
||||
def resize(self):
|
||||
self.need_resize = False
|
||||
self.header_win.resize_columns({
|
||||
'room@server/nickname': self.width//3,
|
||||
'password': self.width//3,
|
||||
'autojoin': self.width//6,
|
||||
'storage': self.width//6
|
||||
})
|
||||
info_height = self.core.information_win_size
|
||||
tab_height = Tab.tab_win_height()
|
||||
self.header_win.resize(1, self.width, 0, 0)
|
||||
self.bookmarks_win.resize(self.height - 3 - tab_height - info_height,
|
||||
self.width, 1, 0)
|
||||
self.help_win.resize(1, self.width, self.height - 1, 0)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - tab_height - info_height, 0)
|
||||
|
||||
def on_info_win_size_changed(self):
|
||||
if self.core.information_win_size >= self.height - 3:
|
||||
return
|
||||
info_height = self.core.information_win_size
|
||||
tab_height = Tab.tab_win_height()
|
||||
self.bookmarks_win.resize(self.height - 3 - tab_height - info_height,
|
||||
self.width, 1, 0)
|
||||
self.info_header.resize(1, self.width,
|
||||
self.height - 2 - tab_height - info_height, 0)
|
||||
|
||||
def refresh(self):
|
||||
if self.need_resize:
|
||||
self.resize()
|
||||
self.header_win.refresh()
|
||||
self.refresh_tab_win()
|
||||
self.help_win.refresh()
|
||||
self.info_header.refresh(self.bookmarks.preferred)
|
||||
self.info_win.refresh()
|
||||
self.bookmarks_win.refresh()
|
||||
|
||||
|
||||
def find_duplicates(bm_list):
|
||||
jids = set()
|
||||
for bookmark in bm_list:
|
||||
if bookmark.jid in jids:
|
||||
return True
|
||||
jids.add(bookmark.jid)
|
||||
return False
|
||||
|
|
@ -5,10 +5,11 @@ used to display information on the screen
|
|||
|
||||
from . base_wins import Win
|
||||
from . data_forms import FormWin
|
||||
from . bookmark_forms import BookmarksWin
|
||||
from . info_bar import GlobalInfoBar, VerticalGlobalInfoBar
|
||||
from . info_wins import InfoWin, XMLInfoWin, PrivateInfoWin, MucListInfoWin, \
|
||||
ConversationInfoWin, DynamicConversationInfoWin, MucInfoWin, \
|
||||
ConversationStatusMessageWin
|
||||
ConversationStatusMessageWin, BookmarksInfoWin
|
||||
from . input_placeholders import HelpText, YesNoInput
|
||||
from . inputs import Input, HistoryInput, MessageInput, CommandInput
|
||||
from . list import ListWin, ColumnHeaderWin
|
||||
|
|
277
src/windows/bookmark_forms.py
Normal file
277
src/windows/bookmark_forms.py
Normal file
|
@ -0,0 +1,277 @@
|
|||
"""
|
||||
Windows used inthe bookmarkstab
|
||||
"""
|
||||
import curses
|
||||
|
||||
from . import Win
|
||||
from . inputs import Input
|
||||
from . data_forms import FieldInput
|
||||
from theming import to_curses_attr, get_theme
|
||||
from common import safeJID
|
||||
|
||||
class BookmarkJIDInput(FieldInput, Input):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Input.__init__(self)
|
||||
jid = safeJID(field.jid)
|
||||
jid.resource = field.nick
|
||||
self.text = jid.full
|
||||
self.pos = len(self.text)
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def save(self):
|
||||
jid = safeJID(self.get_text())
|
||||
self._field.jid = jid.bare
|
||||
self._field.name = jid.bare
|
||||
self._field.nick = jid.resource
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the text'
|
||||
|
||||
class BookmarkMethodInput(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
self.options = ('local', 'remote')
|
||||
# val_pos is the position of the currently selected option
|
||||
self.val_pos = self.options.index(field.method)
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
else:
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
if self.options:
|
||||
option = self.options[self.val_pos]
|
||||
self.addstr(0, self.width//2-len(option)//2, option)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def save(self):
|
||||
self._field.method = self.options[self.val_pos]
|
||||
|
||||
def get_help_message(self):
|
||||
return '←, →: Select a value amongst the others'
|
||||
|
||||
class BookmarkPasswordInput(FieldInput, Input):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Input.__init__(self)
|
||||
self.text = field.password or ''
|
||||
self.pos = len(self.text)
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def rewrite_text(self):
|
||||
self._win.erase()
|
||||
if self.color:
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
|
||||
if self.color:
|
||||
(y, x) = self._win.getyx()
|
||||
size = self.width-x
|
||||
self.addnstr(' '*size, size, to_curses_attr(self.color))
|
||||
self.addstr(0, self.pos, '')
|
||||
if self.color:
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def save(self):
|
||||
self._field.password = self.get_text() or None
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the secret text'
|
||||
|
||||
class BookmarkAutojoinWin(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
self.last_key = 'KEY_RIGHT'
|
||||
self.value = field.autojoin
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT' or key == 'KEY_RIGHT':
|
||||
self.value = not self.value
|
||||
self.last_key = key
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
format_string = '←{:^%s}→' % 7
|
||||
inp = format_string.format(repr(self.value))
|
||||
self.addstr(0, 0, inp)
|
||||
if self.last_key == 'KEY_RIGHT':
|
||||
self.move(0, 8)
|
||||
else:
|
||||
self.move(0, 0)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def save(self):
|
||||
self._field.autojoin = self.value
|
||||
|
||||
def get_help_message(self):
|
||||
return '← and →: change the value between True and False'
|
||||
|
||||
|
||||
class BookmarksWin(Win):
|
||||
def __init__(self, bookmarks, height, width, y, x):
|
||||
self._win = Win._tab_win.derwin(height, width, y, x)
|
||||
self.scroll_pos = 0
|
||||
self._current_input = 0
|
||||
self.current_horizontal_input = 0
|
||||
self._bookmarks = list(bookmarks)
|
||||
self.lines = []
|
||||
for bookmark in sorted(self._bookmarks, key=lambda x: x.jid):
|
||||
self.lines.append((BookmarkJIDInput(bookmark),
|
||||
BookmarkPasswordInput(bookmark),
|
||||
BookmarkAutojoinWin(bookmark),
|
||||
BookmarkMethodInput(bookmark)))
|
||||
|
||||
@property
|
||||
def current_input(self):
|
||||
return self._current_input
|
||||
|
||||
@current_input.setter
|
||||
def current_input(self, value):
|
||||
if 0 <= self._current_input < len(self.lines):
|
||||
if 0 <= value < len(self.lines):
|
||||
self.lines[self._current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self._current_input = value
|
||||
else:
|
||||
self._current_input = 0
|
||||
|
||||
def add_bookmark(self, bookmark):
|
||||
self.lines.append((BookmarkJIDInput(bookmark),
|
||||
BookmarkPasswordInput(bookmark),
|
||||
BookmarkAutojoinWin(bookmark),
|
||||
BookmarkMethodInput(bookmark)))
|
||||
self.current_horizontal_input = 0
|
||||
self.current_input = len(self.lines) - 1
|
||||
if self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos = self.current_input - self.height + 1
|
||||
self.refresh()
|
||||
|
||||
def del_current_bookmark(self):
|
||||
if self.lines:
|
||||
bm = self.lines[self.current_input][0]._field
|
||||
to_delete = self.current_input
|
||||
self.current_input -= 1
|
||||
del self.lines[to_delete]
|
||||
if self.scroll_pos:
|
||||
self.scroll_pos -= 1
|
||||
self.refresh()
|
||||
return bm
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self.height = height
|
||||
self.width = width
|
||||
self._win = Win._tab_win.derwin(height, width, y, x)
|
||||
# Adjust the scroll position, if resizing made the window too small
|
||||
# for the cursor to be visible
|
||||
while self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos += 1
|
||||
|
||||
def go_to_next_line_input(self):
|
||||
if not self.lines:
|
||||
return
|
||||
if self.current_input == len(self.lines) - 1:
|
||||
return
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
# Adjust the scroll position if the current_input would be outside
|
||||
# of the visible area
|
||||
if self.current_input + 1 - self.scroll_pos > self.height-1:
|
||||
self.current_input += 1
|
||||
self.scroll_pos += 1
|
||||
self.refresh()
|
||||
else:
|
||||
self.current_input += 1
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def go_to_previous_line_input(self):
|
||||
if not self.lines:
|
||||
return
|
||||
if self.current_input == 0:
|
||||
return
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_input -= 1
|
||||
# Adjust the scroll position if the current_input would be outside
|
||||
# of the visible area
|
||||
if self.current_input < self.scroll_pos:
|
||||
self.scroll_pos = self.current_input
|
||||
self.refresh()
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def go_to_next_horizontal_input(self):
|
||||
if not self.lines:
|
||||
return
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_horizontal_input += 1
|
||||
if self.current_horizontal_input > 3:
|
||||
self.current_horizontal_input = 0
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def go_to_previous_horizontal_input(self):
|
||||
if not self.lines:
|
||||
return
|
||||
if self.current_horizontal_input == 0:
|
||||
return
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_horizontal_input -= 1
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def on_input(self, key):
|
||||
if not self.lines:
|
||||
return
|
||||
self.lines[self.current_input][self.current_horizontal_input].do_command(key)
|
||||
|
||||
def refresh(self):
|
||||
# store the cursor status
|
||||
self._win.erase()
|
||||
y = - self.scroll_pos
|
||||
for i in range(len(self.lines)):
|
||||
self.lines[i][0].resize(1, self.width//3, y + 1, 0)
|
||||
self.lines[i][1].resize(1, self.width//3, y + 1, self.width//3)
|
||||
self.lines[i][2].resize(1, self.width//6, y + 1, 2*self.width//3)
|
||||
self.lines[i][3].resize(1, self.width//6, y + 1, 5*self.width//6)
|
||||
y += 1
|
||||
self._refresh()
|
||||
for i, inp in enumerate(self.lines):
|
||||
if i < self.scroll_pos:
|
||||
continue
|
||||
if i >= self.height + self.scroll_pos:
|
||||
break
|
||||
for j in range(4):
|
||||
inp[j].refresh()
|
||||
|
||||
if self.lines and self.current_input < self.height-1:
|
||||
self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.lines[self.current_input][self.current_horizontal_input].refresh()
|
||||
if not self.lines:
|
||||
curses.curs_set(0)
|
||||
else:
|
||||
curses.curs_set(1)
|
||||
|
||||
def refresh_current_input(self):
|
||||
if self.lines:
|
||||
self.lines[self.current_input][self.current_horizontal_input].refresh()
|
||||
|
||||
def save(self):
|
||||
for line in self.lines:
|
||||
for item in line:
|
||||
item.save()
|
||||
|
|
@ -469,4 +469,3 @@ class FormWin(object):
|
|||
return self.inputs[self.current_input]['input'].get_help_message()
|
||||
return ''
|
||||
|
||||
|
||||
|
|
|
@ -293,3 +293,17 @@ class ConversationStatusMessageWin(InfoWin):
|
|||
def write_status_message(self, resource):
|
||||
self.addstr(resource.status, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
|
||||
|
||||
class BookmarksInfoWin(InfoWin):
|
||||
def __init__(self):
|
||||
InfoWin.__init__(self)
|
||||
|
||||
def refresh(self, preferred):
|
||||
log.debug('Refresh: %s', self.__class__.__name__)
|
||||
self._win.erase()
|
||||
self.write_remote_status(preferred)
|
||||
self.finish_line(get_theme().COLOR_INFORMATION_BAR)
|
||||
self._refresh()
|
||||
|
||||
def write_remote_status(self, preferred):
|
||||
self.addstr('Remote storage: %s' % preferred, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
|
||||
|
||||
|
|
Loading…
Reference in a new issue