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:
mathieui 2015-02-09 22:13:11 +01:00
parent 2da4474d6a
commit 9141e0c4d3
No known key found for this signature in database
GPG key ID: C59F84CEEFD616E3
7 changed files with 444 additions and 2 deletions

View file

@ -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)

View file

@ -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
View 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

View file

@ -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

View 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()

View file

@ -469,4 +469,3 @@ class FormWin(object):
return self.inputs[self.current_input]['input'].get_help_message()
return ''

View file

@ -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))