Mercurial > libervia-web
comparison src/browser/list_manager.py @ 449:981ed669d3b3
/!\ reorganize all the file hierarchy, move the code and launching script to src:
- browser_side --> src/browser
- public --> src/browser_side/public
- libervia.py --> src/browser/libervia_main.py
- libervia_server --> src/server
- libervia_server/libervia.sh --> src/libervia.sh
- twisted --> src/twisted
- new module src/common
- split constants.py in 3 files:
- src/common/constants.py
- src/browser/constants.py
- src/server/constants.py
- output --> html (generated by pyjsbuild during the installation)
- new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css
- setup.py installs libervia to the following paths:
- src/common --> <LIB>/libervia/common
- src/server --> <LIB>/libervia/server
- src/twisted --> <LIB>/twisted
- html --> <SHARE>/libervia/html
- server_side --> <SHARE>libervia/server_side
- LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation:
- clean: remove previous installation directories
- purge: remove building and previous installation directories
You may need to update your sat.conf and/or launching script to update the following options/parameters:
- ssl_certificate
- data_dir
author | souliane <souliane@mailoo.org> |
---|---|
date | Tue, 20 May 2014 06:41:16 +0200 |
parents | browser_side/list_manager.py@d52f529a6d42 |
children |
comparison
equal
deleted
inserted
replaced
448:14c35f7f1ef5 | 449:981ed669d3b3 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
5 # Copyright (C) 2013, 2014 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 sat.core.log import getLogger | |
21 log = getLogger(__name__) | |
22 from pyjamas.ui.Grid import Grid | |
23 from pyjamas.ui.Button import Button | |
24 from pyjamas.ui.ListBox import ListBox | |
25 from pyjamas.ui.FlowPanel import FlowPanel | |
26 from pyjamas.ui.AutoComplete import AutoCompleteTextBox | |
27 from pyjamas.ui.Label import Label | |
28 from pyjamas.ui.HorizontalPanel import HorizontalPanel | |
29 from pyjamas.ui.VerticalPanel import VerticalPanel | |
30 from pyjamas.ui.DialogBox import DialogBox | |
31 from pyjamas.ui.KeyboardListener import KEY_ENTER | |
32 from pyjamas.ui.MouseListener import MouseHandler | |
33 from pyjamas.ui.FocusListener import FocusHandler | |
34 from pyjamas.ui.DropWidget import DropWidget | |
35 from pyjamas.Timer import Timer | |
36 from pyjamas import DOM | |
37 | |
38 from base_panels import PopupMenuPanel | |
39 from base_widget import DragLabel | |
40 | |
41 # HTML content for the removal button (image or text) | |
42 REMOVE_BUTTON = '<span class="recipientRemoveIcon">x</span>' | |
43 | |
44 # Item to be considered for an empty list box selection. | |
45 # Could be whatever which doesn't look like a JID or a group name. | |
46 EMPTY_SELECTION_ITEM = "" | |
47 | |
48 | |
49 class ListManager(): | |
50 """A manager for sub-panels to assign elements to lists.""" | |
51 | |
52 def __init__(self, parent, keys_dict={}, contact_list=[], offsets={}, style={}): | |
53 """ | |
54 @param parent: FlexTable parent widget for the manager | |
55 @param keys_dict: dict with the contact keys mapped to data | |
56 @param contact_list: list of string (the contact JID userhosts) | |
57 @param offsets: dict to set widget positions offset within parent | |
58 - "x_first": the x offset for the first widget's row on the grid | |
59 - "x": the x offset for all widgets rows, except the first one if "x_first" is defined | |
60 - "y": the y offset for all widgets columns on the grid | |
61 """ | |
62 self._parent = parent | |
63 if isinstance(keys_dict, set) or isinstance(keys_dict, list): | |
64 tmp = {} | |
65 for key in keys_dict: | |
66 tmp[key] = {} | |
67 keys_dict = tmp | |
68 self.__keys_dict = keys_dict | |
69 if isinstance(contact_list, set): | |
70 contact_list = list(contact_list) | |
71 self.__list = contact_list | |
72 self.__list.sort() | |
73 # store the list of contacts that are not assigned yet | |
74 self.__remaining_list = [] | |
75 self.__remaining_list.extend(self.__list) | |
76 # mark a change to sort the list before it's used | |
77 self.__remaining_list_sorted = True | |
78 | |
79 self.offsets = {"x_first": 0, "x": 0, "y": 0} | |
80 if "x" in offsets and not "x_first" in offsets: | |
81 offsets["x_first"] = offsets["x"] | |
82 self.offsets.update(offsets) | |
83 | |
84 self.style = { | |
85 "keyItem": "recipientTypeItem", | |
86 "popupMenuItem": "recipientTypeItem", | |
87 "buttonCell": "recipientButtonCell", | |
88 "dragoverPanel": "dragover-recipientPanel", | |
89 "keyPanel": "recipientPanel", | |
90 "textBox": "recipientTextBox", | |
91 "textBox-invalid": "recipientTextBox-invalid", | |
92 "removeButton": "recipientRemoveButton", | |
93 } | |
94 self.style.update(style) | |
95 | |
96 def createWidgets(self, title_format="%s"): | |
97 """Fill the parent grid with all the widgets (some may be hidden during the initialization).""" | |
98 self.__children = {} | |
99 for key in self.__keys_dict: | |
100 self.addContactKey(key, title_format=title_format) | |
101 | |
102 def addContactKey(self, key, dict_={}, title_format="%s"): | |
103 if key not in self.__keys_dict: | |
104 self.__keys_dict[key] = dict_ | |
105 # copy the key to its associated sub-map | |
106 self.__keys_dict[key]["title"] = key | |
107 self._addChild(self.__keys_dict[key], title_format) | |
108 | |
109 def removeContactKey(self, key): | |
110 """Remove a list panel and all its associated data.""" | |
111 contacts = self.__children[key]["panel"].getContacts() | |
112 (y, x) = self._parent.getIndex(self.__children[key]["button"]) | |
113 self._parent.removeRow(y) | |
114 del self.__children[key] | |
115 del self.__keys_dict[key] | |
116 self.addToRemainingList(contacts) | |
117 | |
118 def _addChild(self, entry, title_format): | |
119 """Add a button and FlowPanel for the corresponding map entry.""" | |
120 button = Button(title_format % entry["title"]) | |
121 button.setStyleName(self.style["keyItem"]) | |
122 if hasattr(entry, "desc"): | |
123 button.setTitle(entry["desc"]) | |
124 if not "optional" in entry: | |
125 entry["optional"] = False | |
126 button.setVisible(not entry["optional"]) | |
127 y = len(self.__children) + self.offsets["y"] | |
128 x = self.offsets["x_first"] if y == self.offsets["y"] else self.offsets["x"] | |
129 | |
130 self._parent.insertRow(y) | |
131 self._parent.setWidget(y, x, button) | |
132 self._parent.getCellFormatter().setStyleName(y, x, self.style["buttonCell"]) | |
133 | |
134 _child = ListPanel(self, entry, self.style) | |
135 self._parent.setWidget(y, x + 1, _child) | |
136 | |
137 self.__children[entry["title"]] = {} | |
138 self.__children[entry["title"]]["button"] = button | |
139 self.__children[entry["title"]]["panel"] = _child | |
140 | |
141 if hasattr(self, "popup_menu"): | |
142 # this is done if self.registerPopupMenuPanel has been called yet | |
143 self.popup_menu.registerClickSender(button) | |
144 | |
145 def _refresh(self, visible=True): | |
146 """Set visible the sub-panels that are non optional or non empty, hide the rest.""" | |
147 for key in self.__children: | |
148 self.setContactPanelVisible(key, False) | |
149 if not visible: | |
150 return | |
151 _map = self.getContacts() | |
152 for key in _map: | |
153 if len(_map[key]) > 0 or not self.__keys_dict[key]["optional"]: | |
154 self.setContactPanelVisible(key, True) | |
155 | |
156 def setVisible(self, visible): | |
157 self._refresh(visible) | |
158 | |
159 def setContactPanelVisible(self, key, visible=True, sender=None): | |
160 """Do not remove the "sender" param as it is needed for the context menu.""" | |
161 self.__children[key]["button"].setVisible(visible) | |
162 self.__children[key]["panel"].setVisible(visible) | |
163 | |
164 @property | |
165 def list(self): | |
166 """Return the full list of potential contacts.""" | |
167 return self.__list | |
168 | |
169 @property | |
170 def keys(self): | |
171 return self.__keys_dict.keys() | |
172 | |
173 @property | |
174 def keys_dict(self): | |
175 return self.__keys_dict | |
176 | |
177 @property | |
178 def remaining_list(self): | |
179 """Return the contacts that have not been selected yet.""" | |
180 if not self.__remaining_list_sorted: | |
181 self.__remaining_list_sorted = True | |
182 self.__remaining_list.sort() | |
183 return self.__remaining_list | |
184 | |
185 def setRemainingListUnsorted(self): | |
186 """Mark a change (deletion) so the list will be sorted before it's used.""" | |
187 self.__remaining_list_sorted = False | |
188 | |
189 def removeFromRemainingList(self, contacts): | |
190 """Remove contacts after they have been added to a sub-panel.""" | |
191 if not isinstance(contacts, list): | |
192 contacts = [contacts] | |
193 for contact_ in contacts: | |
194 if contact_ in self.__remaining_list: | |
195 self.__remaining_list.remove(contact_) | |
196 | |
197 def addToRemainingList(self, contacts, ignore_key=None): | |
198 """Add contacts after they have been removed from a sub-panel.""" | |
199 if not isinstance(contacts, list): | |
200 contacts = [contacts] | |
201 assigned_contacts = set() | |
202 assigned_map = self.getContacts() | |
203 for key_ in assigned_map.keys(): | |
204 if ignore_key is not None and key_ == ignore_key: | |
205 continue | |
206 assigned_contacts.update(assigned_map[key_]) | |
207 for contact_ in contacts: | |
208 if contact_ not in self.__list or contact_ in self.__remaining_list: | |
209 continue | |
210 if contact_ in assigned_contacts: | |
211 continue # the contact is assigned somewhere else | |
212 self.__remaining_list.append(contact_) | |
213 self.setRemainingListUnsorted() | |
214 | |
215 def setContacts(self, _map={}): | |
216 """Set the contacts for each contact key.""" | |
217 for key in self.__keys_dict: | |
218 if key in _map: | |
219 self.__children[key]["panel"].setContacts(_map[key]) | |
220 else: | |
221 self.__children[key]["panel"].setContacts([]) | |
222 self._refresh() | |
223 | |
224 def getContacts(self): | |
225 """Get the contacts for all the lists. | |
226 @return: a mapping between keys and contact lists.""" | |
227 _map = {} | |
228 for key in self.__children: | |
229 _map[key] = self.__children[key]["panel"].getContacts() | |
230 return _map | |
231 | |
232 @property | |
233 def target_drop_cell(self): | |
234 """@return: the panel where something has been dropped.""" | |
235 return self._target_drop_cell | |
236 | |
237 def setTargetDropCell(self, target_drop_cell): | |
238 """@param: target_drop_cell: the panel where something has been dropped.""" | |
239 self._target_drop_cell = target_drop_cell | |
240 | |
241 def registerPopupMenuPanel(self, entries, hide, callback): | |
242 "Register a popup menu panel that will be bound to all contact keys elements." | |
243 self.popup_menu = PopupMenuPanel(entries=entries, hide=hide, callback=callback, style={"item": self.style["popupMenuItem"]}) | |
244 | |
245 | |
246 class DragAutoCompleteTextBox(AutoCompleteTextBox, DragLabel, MouseHandler, FocusHandler): | |
247 """A draggable AutoCompleteTextBox which is used for representing a contact. | |
248 This class is NOT generic because of the onDragEnd method which call methods | |
249 from ListPanel. It's probably not reusable for another scenario. | |
250 """ | |
251 | |
252 def __init__(self, parent, event_cbs, style): | |
253 AutoCompleteTextBox.__init__(self) | |
254 DragLabel.__init__(self, '', 'CONTACT_TEXTBOX') # The group prefix "@" is already in text so we use only the "CONTACT_TEXTBOX" type | |
255 self._parent = parent | |
256 self.event_cbs = event_cbs | |
257 self.style = style | |
258 self.addMouseListener(self) | |
259 self.addFocusListener(self) | |
260 self.addChangeListener(self) | |
261 self.addStyleName(style["textBox"]) | |
262 self.reset() | |
263 | |
264 def reset(self): | |
265 self.setText("") | |
266 self.setValid() | |
267 | |
268 def setValid(self, valid=True): | |
269 if self.getText() == "": | |
270 valid = True | |
271 if valid: | |
272 self.removeStyleName(self.style["textBox-invalid"]) | |
273 else: | |
274 self.addStyleName(self.style["textBox-invalid"]) | |
275 self.valid = valid | |
276 | |
277 def onDragStart(self, event): | |
278 self._text = self.getText() | |
279 DragLabel.onDragStart(self, event) | |
280 self._parent.setTargetDropCell(None) | |
281 self.setSelectionRange(len(self.getText()), 0) | |
282 | |
283 def onDragEnd(self, event): | |
284 target = self._parent.target_drop_cell # parent or another ListPanel | |
285 if self.getText() == "" or target is None: | |
286 return | |
287 self.event_cbs["drop"](self, target) | |
288 | |
289 def setRemoveButton(self): | |
290 | |
291 def remove_cb(sender): | |
292 """Callback for the button to remove this contact.""" | |
293 self._parent.remove(self) | |
294 self._parent.remove(self.remove_btn) | |
295 self.event_cbs["remove"](self) | |
296 | |
297 self.remove_btn = Button(REMOVE_BUTTON, remove_cb, Visible=False) | |
298 self.remove_btn.setStyleName(self.style["removeButton"]) | |
299 self._parent.add(self.remove_btn) | |
300 | |
301 def removeOrReset(self): | |
302 if hasattr(self, "remove_btn"): | |
303 self.remove_btn.click() | |
304 else: | |
305 self.reset() | |
306 | |
307 def onMouseMove(self, sender): | |
308 """Mouse enters the area of a DragAutoCompleteTextBox.""" | |
309 if hasattr(sender, "remove_btn"): | |
310 sender.remove_btn.setVisible(True) | |
311 | |
312 def onMouseLeave(self, sender): | |
313 """Mouse leaves the area of a DragAutoCompleteTextBox.""" | |
314 if hasattr(sender, "remove_btn"): | |
315 Timer(1500, lambda timer: sender.remove_btn.setVisible(False)) | |
316 | |
317 def onFocus(self, sender): | |
318 sender.setSelectionRange(0, len(self.getText())) | |
319 self.event_cbs["focus"](sender) | |
320 | |
321 def validate(self): | |
322 self.setSelectionRange(len(self.getText()), 0) | |
323 self.event_cbs["validate"](self) | |
324 | |
325 def onChange(self, sender): | |
326 """The textbox or list selection is changed""" | |
327 if isinstance(sender, ListBox): | |
328 AutoCompleteTextBox.onChange(self, sender) | |
329 self.validate() | |
330 | |
331 def onClick(self, sender): | |
332 """The list is clicked""" | |
333 AutoCompleteTextBox.onClick(self, sender) | |
334 self.validate() | |
335 | |
336 def onKeyUp(self, sender, keycode, modifiers): | |
337 """Listen for ENTER key stroke""" | |
338 AutoCompleteTextBox.onKeyUp(self, sender, keycode, modifiers) | |
339 if keycode == KEY_ENTER: | |
340 self.validate() | |
341 | |
342 | |
343 class DropCell(DropWidget): | |
344 """A cell where you can drop widgets. This class is NOT generic because of | |
345 onDrop which uses methods from ListPanel. It has been created to | |
346 separate the drag and drop methods from the others and add a bit of | |
347 lisibility, but it's probably not reusable for another scenario. | |
348 """ | |
349 | |
350 def __init__(self, drop_cbs): | |
351 DropWidget.__init__(self) | |
352 self.drop_cbs = drop_cbs | |
353 | |
354 def onDragEnter(self, event): | |
355 self.addStyleName(self.style["dragoverPanel"]) | |
356 DOM.eventPreventDefault(event) | |
357 | |
358 def onDragLeave(self, event): | |
359 if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop()\ | |
360 or event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth() - 1\ | |
361 or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight() - 1: | |
362 # We check that we are inside widget's box, and we don't remove the style in this case because | |
363 # if the mouse is over a widget inside the DropWidget, we don't want the style to be removed | |
364 self.removeStyleName(self.style["dragoverPanel"]) | |
365 | |
366 def onDragOver(self, event): | |
367 DOM.eventPreventDefault(event) | |
368 | |
369 def onDrop(self, event): | |
370 DOM.eventPreventDefault(event) | |
371 dt = event.dataTransfer | |
372 # 'text', 'text/plain', and 'Text' are equivalent. | |
373 item, item_type = dt.getData("text/plain").split('\n') # Workaround for webkit, only text/plain seems to be managed | |
374 if item_type and item_type[-1] == '\0': # Workaround for what looks like a pyjamas bug: the \0 should not be there, and | |
375 item_type = item_type[:-1] # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report | |
376 if item_type in self.drop_cbs.keys(): | |
377 self.drop_cbs[item_type](self, item) | |
378 self.removeStyleName(self.style["dragoverPanel"]) | |
379 | |
380 | |
381 VALID = 1 | |
382 INVALID = 2 | |
383 DELETE = 3 | |
384 | |
385 | |
386 class ListPanel(FlowPanel, DropCell): | |
387 """Sub-panel used for each contact key. Beware that pyjamas.ui.FlowPanel | |
388 is not fully implemented yet and can not be used with pyjamas.ui.Label.""" | |
389 | |
390 def __init__(self, parent, entry, style={}): | |
391 """Initialization with a button and a DragAutoCompleteTextBox.""" | |
392 FlowPanel.__init__(self, Visible=(False if entry["optional"] else True)) | |
393 drop_cbs = {"GROUP": lambda panel, item: self.addContact("@%s" % item), | |
394 "CONTACT": lambda panel, item: self.addContact(item), | |
395 "CONTACT_TITLE": lambda panel, item: self.addContact('@@'), | |
396 "CONTACT_TEXTBOX": lambda panel, item: self.setTargetDropCell(panel) | |
397 } | |
398 DropCell.__init__(self, drop_cbs) | |
399 self.style = style | |
400 self.addStyleName(self.style["keyPanel"]) | |
401 self._parent = parent | |
402 self.key = entry["title"] | |
403 self._addTextBox() | |
404 | |
405 def _addTextBox(self, switchPrevious=False): | |
406 """Add a text box to the last position. If switchPrevious is True, simulate | |
407 an insertion before the current last textbox by copying the text and valid state. | |
408 @return: the created textbox or the previous one if switchPrevious is True. | |
409 """ | |
410 if hasattr(self, "_last_textbox"): | |
411 if self._last_textbox.getText() == "": | |
412 return | |
413 self._last_textbox.setRemoveButton() | |
414 else: | |
415 switchPrevious = False | |
416 | |
417 def focus_cb(sender): | |
418 if sender != self._last_textbox: | |
419 # save the current value before it's being modified | |
420 self._parent.addToRemainingList(sender.getText(), ignore_key=self.key) | |
421 sender.setCompletionItems(self._parent.remaining_list) | |
422 | |
423 def remove_cb(sender): | |
424 """Callback for the button to remove this contact.""" | |
425 self._parent.addToRemainingList(sender.getText()) | |
426 self._parent.setRemainingListUnsorted() | |
427 self._last_textbox.setFocus(True) | |
428 | |
429 def drop_cb(sender, target): | |
430 """Callback when the textbox is drag-n-dropped.""" | |
431 parent = sender._parent | |
432 if target != parent and target.addContact(sender.getText()): | |
433 sender.removeOrReset() | |
434 else: | |
435 parent._parent.removeFromRemainingList(sender.getText()) | |
436 | |
437 events_cbs = {"focus": focus_cb, "validate": self.addContact, "remove": remove_cb, "drop": drop_cb} | |
438 textbox = DragAutoCompleteTextBox(self, events_cbs, self.style) | |
439 self.add(textbox) | |
440 if switchPrevious: | |
441 textbox.setText(self._last_textbox.getText()) | |
442 textbox.setValid(self._last_textbox.valid) | |
443 self._last_textbox.reset() | |
444 previous = self._last_textbox | |
445 self._last_textbox = textbox | |
446 return previous if switchPrevious else textbox | |
447 | |
448 def _checkContact(self, contact, modify): | |
449 """ | |
450 @param contact: the contact to check | |
451 @param modify: True if the contact is being modified | |
452 @return: | |
453 - VALID if the contact is valid | |
454 - INVALID if the contact is not valid but can be displayed | |
455 - DELETE if the contact should not be displayed at all | |
456 """ | |
457 def countItemInList(list_, item): | |
458 """For some reason the built-in count function doesn't work...""" | |
459 count = 0 | |
460 for elem in list_: | |
461 if elem == item: | |
462 count += 1 | |
463 return count | |
464 if contact is None or contact == "": | |
465 return DELETE | |
466 if countItemInList(self.getContacts(), contact) > (1 if modify else 0): | |
467 return DELETE | |
468 return VALID if contact in self._parent.list else INVALID | |
469 | |
470 def addContact(self, contact, sender=None): | |
471 """The first parameter type is checked, so it is also possible to call addContact(sender). | |
472 If contact is not defined, sender.getText() is used. If sender is not defined, contact will | |
473 be written to the last textbox and a new textbox is added afterward. | |
474 @param contact: unicode | |
475 @param sender: DragAutoCompleteTextBox instance | |
476 """ | |
477 if isinstance(contact, DragAutoCompleteTextBox): | |
478 sender = contact | |
479 contact = sender.getText() | |
480 valid = self._checkContact(contact, sender is not None) | |
481 if sender is None: | |
482 # method has been called to modify but to add a contact | |
483 if valid == VALID: | |
484 # eventually insert before the last textbox if it's not empty | |
485 sender = self._addTextBox(True) if self._last_textbox.getText() != "" else self._last_textbox | |
486 sender.setText(contact) | |
487 else: | |
488 sender.setValid(valid == VALID) | |
489 if valid != VALID: | |
490 if sender is not None and valid == DELETE: | |
491 sender.removeOrReset() | |
492 return False | |
493 if sender == self._last_textbox: | |
494 self._addTextBox() | |
495 try: | |
496 sender.setVisibleLength(len(contact)) | |
497 except: | |
498 # IndexSizeError: Index or size is negative or greater than the allowed amount | |
499 log.warning("FIXME: len(%s) returns %d... javascript bug?" % (contact, len(contact))) | |
500 self._parent.removeFromRemainingList(contact) | |
501 self._last_textbox.setFocus(True) | |
502 return True | |
503 | |
504 def emptyContacts(self): | |
505 """Empty the list of contacts.""" | |
506 for child in self.getChildren(): | |
507 if hasattr(child, "remove_btn"): | |
508 child.remove_btn.click() | |
509 | |
510 def setContacts(self, tab): | |
511 """Set the contacts.""" | |
512 self.emptyContacts() | |
513 if isinstance(tab, set): | |
514 tab = list(tab) | |
515 tab.sort() | |
516 for contact in tab: | |
517 self.addContact(contact) | |
518 | |
519 def getContacts(self): | |
520 """Get the contacts | |
521 @return: an array of string""" | |
522 tab = [] | |
523 for widget in self.getChildren(): | |
524 if isinstance(widget, DragAutoCompleteTextBox): | |
525 # not to be mixed with EMPTY_SELECTION_ITEM | |
526 if widget.getText() != "": | |
527 tab.append(widget.getText()) | |
528 return tab | |
529 | |
530 @property | |
531 def target_drop_cell(self): | |
532 """@return: the panel where something has been dropped.""" | |
533 return self._parent.target_drop_cell | |
534 | |
535 def setTargetDropCell(self, target_drop_cell): | |
536 """ | |
537 XXX: Property setter here would not make it, you need a proper method! | |
538 @param target_drop_cell: the panel where something has been dropped.""" | |
539 self._parent.setTargetDropCell(target_drop_cell) | |
540 | |
541 | |
542 class ContactChooserPanel(DialogBox): | |
543 """Display the contacts chooser dialog. This has been implemented while | |
544 prototyping and is currently not used. Left for an eventual later use. | |
545 Replaced by the popup menu which allows to add a panel for Cc or Bcc. | |
546 """ | |
547 | |
548 def __init__(self, manager, **kwargs): | |
549 """Display a listbox for each contact key""" | |
550 DialogBox.__init__(self, autoHide=False, centered=True, **kwargs) | |
551 self.setHTML("Select contacts") | |
552 self.manager = manager | |
553 self.listboxes = {} | |
554 self.contacts = manager.getContacts() | |
555 | |
556 container = VerticalPanel(Visible=True) | |
557 container.addStyleName("marginAuto") | |
558 | |
559 grid = Grid(2, len(self.manager.keys_dict)) | |
560 index = -1 | |
561 for key in self.manager.keys_dict: | |
562 index += 1 | |
563 grid.add(Label("%s:" % self.manager.keys_dict[key]["desc"]), 0, index) | |
564 listbox = ListBox() | |
565 listbox.setMultipleSelect(True) | |
566 listbox.setVisibleItemCount(15) | |
567 listbox.addItem(EMPTY_SELECTION_ITEM) | |
568 for element in manager.list: | |
569 listbox.addItem(element) | |
570 self.listboxes[key] = listbox | |
571 grid.add(listbox, 1, index) | |
572 self._reset() | |
573 | |
574 buttons = HorizontalPanel() | |
575 buttons.addStyleName("marginAuto") | |
576 btn_close = Button("Cancel", self.hide) | |
577 buttons.add(btn_close) | |
578 btn_reset = Button("Reset", self._reset) | |
579 buttons.add(btn_reset) | |
580 btn_ok = Button("OK", self._validate) | |
581 buttons.add(btn_ok) | |
582 | |
583 container.add(grid) | |
584 container.add(buttons) | |
585 | |
586 self.add(container) | |
587 self.center() | |
588 | |
589 def _reset(self): | |
590 """Reset the selections.""" | |
591 for key in self.manager.keys_dict: | |
592 listbox = self.listboxes[key] | |
593 for i in xrange(0, listbox.getItemCount()): | |
594 if listbox.getItemText(i) in self.contacts[key]: | |
595 listbox.setItemSelected(i, "selected") | |
596 else: | |
597 listbox.setItemSelected(i, "") | |
598 | |
599 def _validate(self): | |
600 """Sets back the selected contacts to the good sub-panels.""" | |
601 _map = {} | |
602 for key in self.manager.keys_dict: | |
603 selections = self.listboxes[key].getSelectedItemText() | |
604 if EMPTY_SELECTION_ITEM in selections: | |
605 selections.remove(EMPTY_SELECTION_ITEM) | |
606 _map[key] = selections | |
607 self.manager.setContacts(_map) | |
608 self.hide() |