summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/commands.py3
-rw-r--r--src/tabs/__init__.py1
-rw-r--r--src/tabs/bookmarkstab.py147
-rw-r--r--src/windows/__init__.py3
-rw-r--r--src/windows/bookmark_forms.py277
-rw-r--r--src/windows/data_forms.py1
-rw-r--r--src/windows/info_wins.py14
7 files changed, 444 insertions, 2 deletions
diff --git a/src/core/commands.py b/src/core/commands.py
index 03e7276d..cacd474b 100644
--- a/src/core/commands.py
+++ b/src/core/commands.py
@@ -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)
diff --git a/src/tabs/__init__.py b/src/tabs/__init__.py
index eaf41a2f..d0a881a6 100644
--- a/src/tabs/__init__.py
+++ b/src/tabs/__init__.py
@@ -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
diff --git a/src/tabs/bookmarkstab.py b/src/tabs/bookmarkstab.py
new file mode 100644
index 00000000..68cbbf40
--- /dev/null
+++ b/src/tabs/bookmarkstab.py
@@ -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
+
diff --git a/src/windows/__init__.py b/src/windows/__init__.py
index f9ca7108..5ec73961 100644
--- a/src/windows/__init__.py
+++ b/src/windows/__init__.py
@@ -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
diff --git a/src/windows/bookmark_forms.py b/src/windows/bookmark_forms.py
new file mode 100644
index 00000000..402e2155
--- /dev/null
+++ b/src/windows/bookmark_forms.py
@@ -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()
+
diff --git a/src/windows/data_forms.py b/src/windows/data_forms.py
index d6e2cc66..86f33350 100644
--- a/src/windows/data_forms.py
+++ b/src/windows/data_forms.py
@@ -469,4 +469,3 @@ class FormWin(object):
return self.inputs[self.current_input]['input'].get_help_message()
return ''
-
diff --git a/src/windows/info_wins.py b/src/windows/info_wins.py
index 766afb75..80af4602 100644
--- a/src/windows/info_wins.py
+++ b/src/windows/info_wins.py
@@ -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))
+