comparison browser/sat_browser/contact_group.py @ 1124:28e3eb3bb217

files reorganisation and installation rework: - files have been reorganised to follow other SàT projects and usual Python organisation (no more "/src" directory) - VERSION file is now used, as for other SàT projects - replace the overcomplicated setup.py be a more sane one. Pyjamas part is not compiled anymore by setup.py, it must be done separatly - removed check for data_dir if it's empty - installation tested working in virtual env - libervia launching script is now in bin/libervia
author Goffi <goffi@goffi.org>
date Sat, 25 Aug 2018 17:59:48 +0200
parents src/browser/sat_browser/contact_group.py@5d9f6d25c586
children f9091f4e04f0
comparison
equal deleted inserted replaced
1123:63a4b8fe9782 1124:28e3eb3bb217
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # Libervia: a Salut à Toi frontend
5 # Copyright (C) 2013-2016 Adrien Cossa <souliane@mailoo.org>
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from pyjamas.ui.Button import Button
21 from pyjamas.ui.CheckBox import CheckBox
22 from pyjamas.ui.Label import Label
23 from pyjamas.ui.HorizontalPanel import HorizontalPanel
24 from pyjamas.ui.VerticalPanel import VerticalPanel
25 from pyjamas.ui.DialogBox import DialogBox
26 from pyjamas.ui.ScrollPanel import ScrollPanel
27 from pyjamas.ui import HasAlignment
28
29 import dialog
30 import list_manager
31 import contact_panel
32 import contact_list
33 from sat_frontends.tools import jid
34
35
36 unicode = str # FIXME: pyjamas workaround
37
38
39 class ContactGroupManager(list_manager.ListManager):
40
41 def __init__(self, editor, data, contacts, offsets):
42 """
43 @param container (FlexTable): FlexTable parent widget
44 @param keys (dict{unicode: dict{unicode: unicode}}): dict binding items
45 keys to their display config data.
46 @param contacts (list): list of contacts
47 """
48 self.editor = editor
49 list_manager.ListManager.__init__(self, data, contacts)
50 self.registerPopupMenuPanel(entries={"Remove group": {}},
51 callback=lambda sender, key: self.removeGroup(sender))
52
53 def removeGroup(self, sender):
54 group = sender.getHTML()
55
56 def confirm_cb(answer):
57 if answer:
58 list_manager.ListManager.removeList(self, group)
59 self.editor.add_group_panel.groups.remove(group)
60
61 _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to delete the group '%s'?" % group)
62 _dialog.show()
63
64 def tag(self, contacts):
65 list_manager.ListManager.tag(self, contacts)
66 self.editor.updateContactList(contacts)
67
68 def untag(self, contacts, ignore_key=None):
69 list_manager.ListManager.untag(self, contacts, ignore_key)
70 self.editor.updateContactList(contacts)
71
72
73 class ContactGroupEditor(VerticalPanel):
74 """A big panel including a ContactGroupManager and other UI stuff."""
75
76 def __init__(self, host, container=None, onCloseCallback=None):
77 """
78
79 @param host (SatWebFrontend)
80 @param container (PanelBase): parent panel or None to display in a popup
81 @param onCloseCallback (callable)
82 """
83 VerticalPanel.__init__(self, StyleName="contactGroupEditor")
84 self.host = host
85
86 # eventually display in a popup
87 if container is None:
88 container = DialogBox(autoHide=False, centered=True)
89 container.setHTML("Manage contact groups")
90 self.container = container
91 self._on_close_callback = onCloseCallback
92
93 self.all_contacts = contact_list.JIDList(self.host.contact_list.roster)
94 roster_entities_by_group = self.host.contact_list.roster_entities_by_group
95 del roster_entities_by_group[None] # remove the empty group
96 roster_groups = roster_entities_by_group.keys()
97 roster_groups.sort()
98
99 # groups on the left
100 manager = self.initContactGroupManager(roster_entities_by_group)
101 self.add_group_panel = self.initAddGroupPanel(roster_groups)
102 left_container = VerticalPanel(Width="100%")
103 left_container.add(manager)
104 left_container.add(self.add_group_panel)
105 left_container.setCellHorizontalAlignment(self.add_group_panel, HasAlignment.ALIGN_CENTER)
106 left_panel = ScrollPanel(left_container, StyleName="contactGroupManager")
107 left_panel.setAlwaysShowScrollBars(True)
108
109 # contact list on the right
110 east_panel = ScrollPanel(self.initContactList(), StyleName="contactGroupRoster")
111 east_panel.setAlwaysShowScrollBars(True)
112
113 south_panel = self.initCloseSaveButtons()
114
115 main_panel = HorizontalPanel()
116 main_panel.add(left_panel)
117 main_panel.add(east_panel)
118 self.add(Label("You get here an over whole view of your contact groups. There are two ways to assign your contacts to an existing group: write them into auto-completed textboxes or use the right panel to drag and drop them into the group."))
119 self.add(main_panel)
120 self.add(south_panel)
121
122 self.setCellHorizontalAlignment(south_panel, HasAlignment.ALIGN_CENTER)
123
124 # need to be done after the contact list has been initialized
125 self.updateContactList()
126
127 # Hide the contacts list from the main panel to not confuse the user
128 self.restore_contact_panel = False
129 clist = self.host.contact_list_widget
130 if clist.getVisible():
131 self.restore_contact_panel = True
132 self.host.panel._contactsSwitch()
133
134 container.add(self)
135 container.setVisible(True)
136 if isinstance(container, DialogBox):
137 container.center()
138
139 def initContactGroupManager(self, data):
140 """Initialise the contact group manager.
141
142 @param groups (list[unicode]): contact groups
143 """
144 self.groups = ContactGroupManager(self, data, self.all_contacts)
145 return self.groups
146
147 def initAddGroupPanel(self, groups):
148 """Initialise the 'Add group' panel.
149
150 @param groups (list[unicode]): contact groups
151 """
152
153 def add_group_cb(key):
154 self.groups.addList(key)
155 self.add_group_panel.textbox.setFocus(True)
156
157 add_group_panel = dialog.AddGroupPanel(groups, add_group_cb)
158 add_group_panel.addStyleName("addContactGroupPanel")
159 return add_group_panel
160
161 def initCloseSaveButtons(self):
162 """Add the buttons to close the dialog and save the groups."""
163 buttons = HorizontalPanel()
164 buttons.addStyleName("marginAuto")
165 buttons.add(Button("Cancel", listener=self.cancelWithoutSaving))
166 buttons.add(Button("Save", listener=self.closeAndSave))
167 return buttons
168
169 def initContactList(self):
170 """Add the contact list to the DockPanel."""
171
172 self.toggle = CheckBox("Hide assigned contacts")
173 self.toggle.addClickListener(lambda dummy: self.updateContactList())
174 self.toggle.addStyleName("toggleAssignedContacts")
175 self.contacts = contact_panel.ContactsPanel(self.host)
176 for contact in self.all_contacts:
177 self.contacts.updateContactBox(contact)
178 panel = VerticalPanel()
179 panel.add(self.toggle)
180 panel.add(self.contacts)
181 return panel
182
183 def updateContactList(self, contacts=None):
184 """Update the contact list's items visibility, depending of the toggle
185 checkbox and the "contacts" attribute.
186
187 @param contacts (list): contacts to be updated, or None to update all.
188 """
189 if not hasattr(self, "toggle"):
190 return
191 if contacts is not None:
192 contacts = [jid.JID(contact) for contact in contacts]
193 contacts = set(contacts).intersection(self.all_contacts)
194 else:
195 contacts = self.all_contacts
196
197 for contact in contacts:
198 if not self.toggle.getChecked(): # show all contacts
199 self.contacts.updateContactBox(contact).setVisible(True)
200 else: # show only non-assigned contacts
201 if contact in self.groups.untagged:
202 self.contacts.updateContactBox(contact).setVisible(True)
203 else:
204 self.contacts.updateContactBox(contact).setVisible(False)
205
206 def __close(self):
207 """Remove the widget from parent or close the popup."""
208 if isinstance(self.container, DialogBox):
209 self.container.hide()
210 self.container.remove(self)
211 if self._on_close_callback is not None:
212 self._on_close_callback()
213 if self.restore_contact_panel:
214 self.host.panel._contactsSwitch()
215
216 def cancelWithoutSaving(self):
217 """Ask for confirmation before closing the dialog."""
218 def confirm_cb(answer):
219 if answer:
220 self.__close()
221
222 _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to cancel without saving?")
223 _dialog.show()
224
225 def closeAndSave(self):
226 """Call bridge methods to save the changes and close the dialog"""
227 old_groups_by_entity = contact_list.JIDDict(self.host.contact_list.roster_groups_by_entity)
228 old_entities = old_groups_by_entity.keys()
229 result = {jid.JID(item): keys for item, keys in self.groups.getKeysByItem().iteritems()}
230 groups_by_entity = contact_list.JIDDict(result)
231 entities = groups_by_entity.keys()
232
233 for invalid in entities.difference(self.all_contacts):
234 dialog.InfoDialog("Invalid contact(s)",
235 "The contact '%s' is not in your contact list but has been assigned to: '%s'." % (invalid, "', '".join(groups_by_entity[invalid])) +
236 "Your changes could not be saved: please check your assignments and save again.", Width="400px").center()
237 return
238
239 for entity in old_entities.difference(entities):
240 self.host.bridge.call('updateContact', None, unicode(entity), '', [])
241
242 for entity, groups in groups_by_entity.iteritems():
243 if entity not in old_groups_by_entity or groups != old_groups_by_entity[entity]:
244 self.host.bridge.call('updateContact', None, unicode(entity), '', list(groups))
245 self.__close()