1# This file is part of Gajim.
2#
3# Gajim is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published
5# by the Free Software Foundation; version 3 only.
6#
7# Gajim is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License
13# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
14
15import logging
16from enum import IntEnum
17
18from gi.repository import Gtk
19from gi.repository import Gdk
20from nbxmpp.structs import BookmarkData
21from nbxmpp.protocol import validate_resourcepart
22from nbxmpp.protocol import JID
23
24from gajim.common import app
25from gajim.common.helpers import validate_jid
26from gajim.common.i18n import _
27
28from .util import get_builder
29
30
31log = logging.getLogger('gajim.gui.bookmarks')
32
33
34class Column(IntEnum):
35    ADDRESS = 0
36    NAME = 1
37    NICK = 2
38    PASSWORD = 3
39    AUTOJOIN = 4
40
41
42class Bookmarks(Gtk.ApplicationWindow):
43    def __init__(self, account):
44        Gtk.ApplicationWindow.__init__(self)
45        self.set_application(app.app)
46        self.set_position(Gtk.WindowPosition.CENTER)
47        self.set_show_menubar(False)
48        self.set_title(_('Bookmarks for %s') % app.get_account_label(account))
49        self.set_default_size(700, 500)
50
51        self.account = account
52
53        self._ui = get_builder('bookmarks.ui')
54        self.add(self._ui.bookmarks_grid)
55
56        con = app.connections[account]
57        for bookmark in con.get_module('Bookmarks').bookmarks:
58            self._ui.bookmarks_store.append([str(bookmark.jid),
59                                             bookmark.name,
60                                             bookmark.nick,
61                                             bookmark.password,
62                                             bookmark.autojoin])
63
64        self._ui.bookmarks_view.set_search_equal_func(self._search_func)
65
66        self._ui.connect_signals(self)
67        self.connect_after('key-press-event', self._on_key_press)
68
69        self.show_all()
70
71    def _on_key_press(self, _widget, event):
72        if event.keyval == Gdk.KEY_Escape:
73            self.destroy()
74
75    def _on_selection_changed(self, selection):
76        _, iter_ = selection.get_selected()
77        self._ui.remove_button.set_sensitive(iter_ is not None)
78
79    def _on_address_edited(self, _renderer, path, new_value):
80        iter_ = self._ui.bookmarks_store.get_iter(path)
81        if not new_value:
82            return
83
84        try:
85            jid = validate_jid(new_value)
86        except ValueError as error:
87            log.warning('Invalid JID: %s (%s)', error, new_value)
88            return
89
90        if not jid.is_bare:
91            log.warning('Invalid JID: only bare JIDs allowed (%s)', jid)
92            return
93
94        self._ui.bookmarks_store.set_value(iter_,
95                                           Column.ADDRESS,
96                                           new_value or None)
97
98    def _on_name_edited(self, _renderer, path, new_value):
99        iter_ = self._ui.bookmarks_store.get_iter(path)
100        self._ui.bookmarks_store.set_value(iter_,
101                                           Column.NAME,
102                                           new_value or None)
103
104    def _on_nick_edited(self, _renderer, path, new_value):
105        iter_ = self._ui.bookmarks_store.get_iter(path)
106
107        if new_value:
108            try:
109                validate_resourcepart(new_value)
110            except ValueError as error:
111                log.warning('Invalid nickname: %s', error)
112                return
113
114        self._ui.bookmarks_store.set_value(iter_,
115                                           Column.NICK,
116                                           new_value or None)
117
118    def _on_password_edited(self, _renderer, path, new_value):
119        iter_ = self._ui.bookmarks_store.get_iter(path)
120        self._ui.bookmarks_store.set_value(iter_,
121                                           Column.PASSWORD,
122                                           new_value or None)
123
124    def _on_autojoin_toggled(self, _renderer, path):
125        iter_ = self._ui.bookmarks_store.get_iter(path)
126        new_value = not self._ui.bookmarks_store[iter_][Column.AUTOJOIN]
127        self._ui.bookmarks_store.set_value(iter_, Column.AUTOJOIN, new_value)
128
129    def _on_add_clicked(self, _button):
130        iter_ = self._ui.bookmarks_store.append([None, None, None, None, False])
131        self._ui.bookmarks_view.get_selection().select_iter(iter_)
132        path = self._ui.bookmarks_store.get_path(iter_)
133        self._ui.bookmarks_view.scroll_to_cell(path, None, False)
134
135    def _on_remove_clicked(self, _button):
136        mod, paths = self._ui.bookmarks_view.get_selection().get_selected_rows()
137        for path in paths:
138            iter_ = mod.get_iter(path)
139            self._ui.bookmarks_store.remove(iter_)
140
141    def _on_apply_clicked(self, _button):
142        bookmarks = []
143        for row in self._ui.bookmarks_store:
144            if not row[Column.ADDRESS]:
145                continue
146
147            bookmark = BookmarkData(jid=JID.from_string(row[Column.ADDRESS]),
148                                    name=row[Column.NAME],
149                                    autojoin=row[Column.AUTOJOIN],
150                                    password=row[Column.PASSWORD],
151                                    nick=row[Column.NICK])
152            bookmarks.append(bookmark)
153
154        con = app.connections[self.account]
155        con.get_module('Bookmarks').store_difference(bookmarks)
156        self.destroy()
157
158    @staticmethod
159    def _search_func(model, _column, search_text, iter_):
160        return search_text.lower() not in model[iter_][0].lower()
161