comparison browser_side/panels.py @ 210:3092f6b1710c

browser side: make the OK button title for group selector configureable + few "cosmetic" changes (PEP 8...) fix bug 15 fix bug 33
author souliane <souliane@mailoo.org>
date Fri, 06 Sep 2013 15:40:33 +0200
parents 4d7054542751
children 8bbac49765d6
comparison
equal deleted inserted replaced
209:4564c7bc06a7 210:3092f6b1710c
46 import dialog 46 import dialog
47 import base_widget 47 import base_widget
48 from pyjamas import Window 48 from pyjamas import Window
49 from __pyjamas__ import doc 49 from __pyjamas__ import doc
50 50
51
51 class UniBoxPanel(SimplePanel): 52 class UniBoxPanel(SimplePanel):
52 """Panel containing the UniBox""" 53 """Panel containing the UniBox"""
53 54
54 def __init__(self, host): 55 def __init__(self, host):
55 SimplePanel.__init__(self) 56 SimplePanel.__init__(self)
56 self.setStyleName('uniBoxPanel') 57 self.setStyleName('uniBoxPanel')
57 self.unibox = UniBox(host) 58 self.unibox = UniBox(host)
58 self.unibox.setWidth('100%') 59 self.unibox.setWidth('100%')
59 self.add(self.unibox) 60 self.add(self.unibox)
60 61
62
61 class UniBox(TextArea, MouseHandler): #AutoCompleteTextBox): 63 class UniBox(TextArea, MouseHandler): #AutoCompleteTextBox):
62 """This text box is used as a main typing point, for message, microblog, etc""" 64 """This text box is used as a main typing point, for message, microblog, etc"""
63 65
64 def __init__(self, host): 66 def __init__(self, host):
65 TextArea.__init__(self) 67 TextArea.__init__(self)
66 #AutoCompleteTextBox.__init__(self) 68 #AutoCompleteTextBox.__init__(self)
67 self.__size = (0,0) 69 self.__size = (0, 0)
68 self._popup = None 70 self._popup = None
69 self._timer = Timer(notify=self._timeCb) 71 self._timer = Timer(notify=self._timeCb)
70 self.host = host 72 self.host = host
71 self.setStyleName('uniBox') 73 self.setStyleName('uniBox')
72 self.addKeyboardListener(self) 74 self.addKeyboardListener(self)
82 def removeKey(self, key): 84 def removeKey(self, key):
83 try: 85 try:
84 self.getCompletionItems().completions.remove(key) 86 self.getCompletionItems().completions.remove(key)
85 except KeyError: 87 except KeyError:
86 print "WARNING: trying to remove an unknown key" 88 print "WARNING: trying to remove an unknown key"
87
88 89
89 def showWarning(self, target_data): 90 def showWarning(self, target_data):
90 target_hook, _type, msg = target_data 91 target_hook, _type, msg = target_data
91 if _type == "NONE": 92 if _type == "NONE":
92 return 93 return
113 self._popup.setStyleName("warningPopup") 114 self._popup.setStyleName("warningPopup")
114 if style: 115 if style:
115 self._popup.addStyleName(style) 116 self._popup.addStyleName(style)
116 117
117 left = 0 118 left = 0
118 top = 0 #max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2) 119 top = 0 #max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2)
119 self._popup.setPopupPosition(left, top) 120 self._popup.setPopupPosition(left, top)
120 self._popup.show() 121 self._popup.show()
121 122
122 def _timeCb(self, timer): 123 def _timeCb(self, timer):
123 if self._popup: 124 if self._popup:
219 220
220 #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done 221 #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done
221 #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this 222 #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this
222 return AutoCompleteTextBox.complete(self)""" 223 return AutoCompleteTextBox.complete(self)"""
223 224
225
224 class MicroblogItem(): 226 class MicroblogItem():
225 #XXX: should be moved in a separated module 227 #XXX: should be moved in a separated module
226 228
227 def __init__(self, data): 229 def __init__(self, data):
228 self.id = data['id'] 230 self.id = data['id']
229 self.type = data.get('type','main_item') 231 self.type = data.get('type', 'main_item')
230 self.content = data['content'] 232 self.content = data['content']
231 self.author = data['author'] 233 self.author = data['author']
232 self.timestamp = float(data.get('timestamp',0)) #XXX: int doesn't work here 234 self.timestamp = float(data.get('timestamp', 0)) #XXX: int doesn't work here
233 self.comments = data.get('comments', False) 235 self.comments = data.get('comments', False)
234 if self.comments: 236 if self.comments:
235 try: 237 try:
236 self.comments_hash = (data['comments_service'], data['comments_node']) 238 self.comments_hash = (data['comments_service'], data['comments_node'])
237 self.comments_service = data['comments_service'] 239 self.comments_service = data['comments_service']
241 self.comments = False 243 self.comments = False
242 if set(("service","node")).issubset(data.keys()): 244 if set(("service","node")).issubset(data.keys()):
243 self.service = data["service"] 245 self.service = data["service"]
244 self.node = data["node"] 246 self.node = data["node"]
245 self.hash = (self.service, self.node) 247 self.hash = (self.service, self.node)
248
246 249
247 class MicroblogEntry(SimplePanel, ClickHandler): 250 class MicroblogEntry(SimplePanel, ClickHandler):
248 251
249 def __init__(self, blog_panel, mblog_entry): 252 def __init__(self, blog_panel, mblog_entry):
250 SimplePanel.__init__(self) 253 SimplePanel.__init__(self)
332 # we have a meta MicroblogPanel, we publish publicly 335 # we have a meta MicroblogPanel, we publish publicly
333 return ("PUBLIC", self.warning_msg_public) 336 return ("PUBLIC", self.warning_msg_public)
334 else: 337 else:
335 # we only accept one group at the moment 338 # we only accept one group at the moment
336 # FIXME: manage several groups 339 # FIXME: manage several groups
337 return ("GROUP", self.warning_msg_group % self.accepted_groups[0]) 340 return ("GROUP", self.warning_msg_group % self.accepted_groups[0])
338 341
339 def onTextEntered(self, text): 342 def onTextEntered(self, text):
340 if self.selected_entry: 343 if self.selected_entry:
341 # we are entering a comment 344 # we are entering a comment
342 comments_node = self.selected_entry.comments 345 comments_node = self.selected_entry.comments
381 print ("WARNING: No content found in microblog [%s]", mblog) 384 print ("WARNING: No content found in microblog [%s]", mblog)
382 continue 385 continue
383 mblog_item = MicroblogItem(mblog) 386 mblog_item = MicroblogItem(mblog)
384 self.addEntry(mblog_item) 387 self.addEntry(mblog_item)
385 388
386 def _chronoInsert(self, vpanel, entry, reverse = True): 389 def _chronoInsert(self, vpanel, entry, reverse=True):
387 """ Insert an entry in chronological order 390 """ Insert an entry in chronological order
388 @param vpanel: VerticalPanel instance 391 @param vpanel: VerticalPanel instance
389 @param entry: MicroblogEntry 392 @param entry: MicroblogEntry
390 @param reverse: more recent entry on top if True, chronological order else""" 393 @param reverse: more recent entry on top if True, chronological order else"""
391 # we look for the right index to insert our entry: 394 # we look for the right index to insert our entry:
393 # in the past 396 # in the past
394 idx = 0 397 idx = 0
395 398
396 for child in vpanel.children: 399 for child in vpanel.children:
397 if not isinstance(child, MicroblogEntry): 400 if not isinstance(child, MicroblogEntry):
398 idx+=1 401 idx += 1
399 continue 402 continue
400 if reverse: 403 if reverse:
401 if child.timestamp < entry.timestamp: 404 if child.timestamp < entry.timestamp:
402 break 405 break
403 else: 406 else:
404 if child.timestamp > entry.timestamp: 407 if child.timestamp > entry.timestamp:
405 break 408 break
406 idx+=1 409 idx += 1
407 410
408 vpanel.insert(entry, idx) 411 vpanel.insert(entry, idx)
409 412
410 def addEntry(self, mblog_item): 413 def addEntry(self, mblog_item):
411 """Add an entry to the panel 414 """Add an entry to the panel
453 self.selected_entry.removeStyleName('selected_entry') 456 self.selected_entry.removeStyleName('selected_entry')
454 if entry: 457 if entry:
455 entry.addStyleName('selected_entry') 458 entry.addStyleName('selected_entry')
456 self.selected_entry = entry 459 self.selected_entry = entry
457 460
458
459 def updateValue(self, type, jid, value): 461 def updateValue(self, type, jid, value):
460 """Update a jid value in entries 462 """Update a jid value in entries
461 @param type: one of 'avatar', 'nick' 463 @param type: one of 'avatar', 'nick'
462 @param jid: jid concerned 464 @param jid: jid concerned
463 @param value: new value""" 465 @param value: new value"""
465 for child in vpanel.children: 467 for child in vpanel.children:
466 if isinstance(child, MicroblogEntry) and child.author == jid: 468 if isinstance(child, MicroblogEntry) and child.author == jid:
467 child.updateAvatar(value) 469 child.updateAvatar(value)
468 elif isinstance(child, VerticalPanel): 470 elif isinstance(child, VerticalPanel):
469 updateVPanel(child) 471 updateVPanel(child)
470 if type=='avatar': 472 if type == 'avatar':
471 updateVPanel(self.vpanel) 473 updateVPanel(self.vpanel)
472 474
473 def setAcceptedGroup(self, group): 475 def setAcceptedGroup(self, group):
474 """Set the group which can be displayed in this panel 476 """Set the group which can be displayed in this panel
475 @param group: string of the group, or list of string 477 @param group: string of the group, or list of string
488 for group in self.accepted_groups: 490 for group in self.accepted_groups:
489 if self.host.contact_panel.isContactInGroup(group, jid): 491 if self.host.contact_panel.isContactInGroup(group, jid):
490 return True 492 return True
491 return False 493 return False
492 494
495
493 class StatusPanel(HTMLPanel, ClickHandler): 496 class StatusPanel(HTMLPanel, ClickHandler):
494 def __init__(self, host, status=''): 497 def __init__(self, host, status=''):
495 self.host = host 498 self.host = host
496 self.status = status or '&nbsp;' 499 self.status = status or '&nbsp;'
497 HTMLPanel.__init__(self, self.__getContent()) 500 HTMLPanel.__init__(self, self.__getContent())
498 self.setStyleName('statusPanel') 501 self.setStyleName('statusPanel')
499 ClickHandler.__init__(self) 502 ClickHandler.__init__(self)
500 self.addClickListener(self) 503 self.addClickListener(self)
501 504
502 def __getContent(self): 505 def __getContent(self):
503 return "<span class='status'>%(status)s</span>" % {'status':html_sanitize(self.status)} 506 return "<span class='status'>%(status)s</span>" % {'status': html_sanitize(self.status)}
504 507
505 def changeStatus(self, new_status): 508 def changeStatus(self, new_status):
506 self.status = new_status or '&nbsp;' 509 self.status = new_status or '&nbsp;'
507 self.setHTML(self.__getContent()) 510 self.setHTML(self.__getContent())
508 511
509 def onClick(self, sender): 512 def onClick(self, sender):
510 #As status is the default target of uniBar, we don't want to select anything if click on it 513 #As status is the default target of uniBar, we don't want to select anything if click on it
511 self.host.setSelected(None) 514 self.host.setSelected(None)
515
512 516
513 class ChatText(HTMLPanel): 517 class ChatText(HTMLPanel):
514 518
515 def __init__(self, timestamp, nick, mymess, msg): 519 def __init__(self, timestamp, nick, mymess, msg):
516 _date = datetime.fromtimestamp(float(timestamp or time())) 520 _date = datetime.fromtimestamp(float(timestamp or time()))
517 _msg_class = ["chat_text_msg"] 521 _msg_class = ["chat_text_msg"]
518 if mymess: 522 if mymess:
519 _msg_class.append("chat_text_mymess") 523 _msg_class.append("chat_text_mymess")
520 HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" % 524 HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" %
521 {"timestamp": _date.strftime("%H:%M"), 525 {"timestamp": _date.strftime("%H:%M"),
522 "nick": "[%s]" % html_sanitize(nick), 526 "nick": "[%s]" % html_sanitize(nick),
523 "msg_class": ' '.join(_msg_class), 527 "msg_class": ' '.join(_msg_class),
524 "msg": html_sanitize(msg)} 528 "msg": html_sanitize(msg)}
525 ) 529 )
526 self.setStyleName('chatText') 530 self.setStyleName('chatText')
531
527 532
528 class Occupant(HTML): 533 class Occupant(HTML):
529 """Occupant of a MUC room""" 534 """Occupant of a MUC room"""
530 535
531 def __init__(self, nick): 536 def __init__(self, nick):
532 self.nick = nick 537 self.nick = nick
533 HTML.__init__(self, "<div class='occupant'>%s</div>" % html_sanitize(nick)) 538 HTML.__init__(self, "<div class='occupant'>%s</div>" % html_sanitize(nick))
534 539
535 def __str__(self): 540 def __str__(self):
536 return self.nick 541 return self.nick
542
537 543
538 class OccupantsList(AbsolutePanel): 544 class OccupantsList(AbsolutePanel):
539 """Panel user to show occupants of a room""" 545 """Panel user to show occupants of a room"""
540 546
541 def __init__(self): 547 def __init__(self):
556 562
557 def clear(self): 563 def clear(self):
558 self.occupants_list.clear() 564 self.occupants_list.clear()
559 AbsolutePanel.clear(self) 565 AbsolutePanel.clear(self)
560 566
567
561 class ChatPanel(base_widget.LiberviaWidget): 568 class ChatPanel(base_widget.LiberviaWidget):
562 569
563 def __init__(self, host, target, type_='one2one'): 570 def __init__(self, host, target, type_='one2one'):
564 """Panel used for conversation (one 2 one or group chat) 571 """Panel used for conversation (one 2 one or group chat)
565 @param host: SatWebFrontend instance 572 @param host: SatWebFrontend instance
566 @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room) 573 @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room)
567 @param type: one2one for simple conversation, group for MUC""" 574 @param type: one2one for simple conversation, group for MUC"""
568 base_widget.LiberviaWidget.__init__(self, host, target.bare, selectable = True) 575 base_widget.LiberviaWidget.__init__(self, host, target.bare, selectable=True)
569 self.vpanel = VerticalPanel() 576 self.vpanel = VerticalPanel()
570 self.vpanel.setSize('100%','100%') 577 self.vpanel.setSize('100%', '100%')
571 self.type = type_ 578 self.type = type_
572 self.nick = None 579 self.nick = None
573 if not target: 580 if not target:
574 print "ERROR: Empty target !" 581 print "ERROR: Empty target !"
575 return 582 return
621 elif self.type == "group": 628 elif self.type == "group":
622 msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target 629 msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target
623 return ("ONE2ONE" if self.type == "one2one" else "GROUP", msg) 630 return ("ONE2ONE" if self.type == "one2one" else "GROUP", msg)
624 631
625 def onTextEntered(self, text): 632 def onTextEntered(self, text):
626 mess_type = "groupchat" if self.type=='group' else "chat" 633 mess_type = "groupchat" if self.type == 'group' else "chat"
627 self.host.bridge.call('sendMessage', None, str(self.target), text, '', mess_type) 634 self.host.bridge.call('sendMessage', None, str(self.target), text, '', mess_type)
628 635
629 def onQuit(self): 636 def onQuit(self):
630 base_widget.LiberviaWidget.onQuit(self) 637 base_widget.LiberviaWidget.onQuit(self)
631 if self.type == 'group': 638 if self.type == 'group':
632 self.host.bridge.call('mucLeave', None, self.target.bare) 639 self.host.bridge.call('mucLeave', None, self.target.bare)
633
634 640
635 def setUserNick(self, nick): 641 def setUserNick(self, nick):
636 """Set the nick of the user, usefull for e.g. change the color of the user""" 642 """Set the nick of the user, usefull for e.g. change the color of the user"""
637 self.nick = nick 643 self.nick = nick
638 644
673 _wid.setStyleName('chatTextMe') 679 _wid.setStyleName('chatTextMe')
674 else: 680 else:
675 _wid.setStyleName('chatTextInfo') 681 _wid.setStyleName('chatTextInfo')
676 self.content.add(_wid) 682 self.content.add(_wid)
677 683
678
679 def printMessage(self, from_jid, msg, timestamp=None): 684 def printMessage(self, from_jid, msg, timestamp=None):
680 """Print message in chat window. Must be implemented by child class""" 685 """Print message in chat window. Must be implemented by child class"""
681 _jid=JID(from_jid) 686 _jid = JID(from_jid)
682 nick = _jid.node if self.type=='one2one' else _jid.resource 687 nick = _jid.node if self.type == 'one2one' else _jid.resource
683 mymess = _jid.resource == self.nick if self.type == "group" else _jid.bare == self.host.whoami.bare #mymess = True if message comes from local user 688 mymess = _jid.resource == self.nick if self.type == "group" else _jid.bare == self.host.whoami.bare #mymess = True if message comes from local user
684 if msg.startswith('/me '): 689 if msg.startswith('/me '):
685 self.printInfo('* %s %s' % (nick, msg[4:]),type='me') 690 self.printInfo('* %s %s' % (nick, msg[4:]), type='me')
686 return 691 return
687 self.content.add(ChatText(timestamp, nick, mymess, msg)) 692 self.content.add(ChatText(timestamp, nick, mymess, msg))
688 self.content_scroll.scrollToBottom() 693 self.content_scroll.scrollToBottom()
689 694
690 def startGame(self, game_type, referee, players): 695 def startGame(self, game_type, referee, players):
691 """Configure the chat window to start a game""" 696 """Configure the chat window to start a game"""
692 if game_type=="Tarot": 697 if game_type == "Tarot":
693 if hasattr(self, "tarot_panel"): 698 if hasattr(self, "tarot_panel"):
694 return 699 return
695 self.tarot_panel = CardPanel(self, referee, players, self.nick) 700 self.tarot_panel = CardPanel(self, referee, players, self.nick)
696 self.vpanel.insert(self.tarot_panel, 0) 701 self.vpanel.insert(self.tarot_panel, 0)
697 self.vpanel.setCellHeight(self.tarot_panel, self.tarot_panel.getHeight()) 702 self.vpanel.setCellHeight(self.tarot_panel, self.tarot_panel.getHeight())
698 elif game_type=="RadioCol": 703 elif game_type == "RadioCol":
699 #XXX: We can have double panel if we join quickly enough to have the group chat start signal 704 #XXX: We can have double panel if we join quickly enough to have the group chat start signal
700 # on invitation + the one triggered on room join 705 # on invitation + the one triggered on room join
701 if hasattr(self, "radiocol_panel"): 706 if hasattr(self, "radiocol_panel"):
702 return 707 return
703 self.radiocol_panel = RadioColPanel(self, referee, self.nick) 708 self.radiocol_panel = RadioColPanel(self, referee, self.nick)
705 self.vpanel.setCellHeight(self.radiocol_panel, self.radiocol_panel.getHeight()) 710 self.vpanel.setCellHeight(self.radiocol_panel, self.radiocol_panel.getHeight())
706 711
707 def getGame(self, game_type): 712 def getGame(self, game_type):
708 """Return class managing the game type""" 713 """Return class managing the game type"""
709 #TODO: check that the game is launched, and manage errors 714 #TODO: check that the game is launched, and manage errors
710 if game_type=="Tarot": 715 if game_type == "Tarot":
711 return self.tarot_panel 716 return self.tarot_panel
712 elif game_type=="RadioCol": 717 elif game_type == "RadioCol":
713 return self.radiocol_panel 718 return self.radiocol_panel
719
714 720
715 class WebPanel(base_widget.LiberviaWidget): 721 class WebPanel(base_widget.LiberviaWidget):
716 """ (mini)browser like widget """ 722 """ (mini)browser like widget """
717 723
718 def __init__(self, host, url=None): 724 def __init__(self, host, url=None):
720 @param host: SatWebFrontend instance 726 @param host: SatWebFrontend instance
721 """ 727 """
722 base_widget.LiberviaWidget.__init__(self, host) 728 base_widget.LiberviaWidget.__init__(self, host)
723 self._vpanel = VerticalPanel() 729 self._vpanel = VerticalPanel()
724 self._vpanel.setSize('100%', '100%') 730 self._vpanel.setSize('100%', '100%')
725 self._url = dialog.ExtTextBox(enter_cb = self.onUrlClick) 731 self._url = dialog.ExtTextBox(enter_cb=self.onUrlClick)
726 self._url.setText(url or "") 732 self._url.setText(url or "")
727 self._url.setWidth('100%') 733 self._url.setWidth('100%')
728 hpanel = HorizontalPanel() 734 hpanel = HorizontalPanel()
729 hpanel.add(self._url) 735 hpanel.add(self._url)
730 btn = Button("Go", self.onUrlClick) 736 btn = Button("Go", self.onUrlClick)
741 self.setWidget(self._vpanel) 747 self.setWidget(self._vpanel)
742 748
743 def onUrlClick(self, sender): 749 def onUrlClick(self, sender):
744 self._frame.setUrl(self._url.getText()) 750 self._frame.setUrl(self._url.getText())
745 751
752
746 class MainPanel(AbsolutePanel): 753 class MainPanel(AbsolutePanel):
747 754
748 def __init__(self, host): 755 def __init__(self, host):
749 self.host=host 756 self.host = host
750 AbsolutePanel.__init__(self) 757 AbsolutePanel.__init__(self)
751 758
752 #menu 759 #menu
753 menu = Menu(host) 760 menu = Menu(host)
754 761
771 self.tab_panel = base_widget.MainTabPanel(host) 778 self.tab_panel = base_widget.MainTabPanel(host)
772 self.discuss_panel = base_widget.WidgetsPanel(self.host, locked=True) 779 self.discuss_panel = base_widget.WidgetsPanel(self.host, locked=True)
773 self.tab_panel.add(self.discuss_panel, "Discussions") 780 self.tab_panel.add(self.discuss_panel, "Discussions")
774 self.tab_panel.selectTab(0) 781 self.tab_panel.selectTab(0)
775 782
776 header=AbsolutePanel() 783 header = AbsolutePanel()
777 header.add(menu) 784 header.add(menu)
778 header.add(unibox_panel) 785 header.add(unibox_panel)
779 header.add(status) 786 header.add(status)
780 header.setStyleName('header') 787 header.setStyleName('header')
781 self.add(header) 788 self.add(header)
800 if not _elts.length: 807 if not _elts.length:
801 tab_bar_h = 0 808 tab_bar_h = 0
802 else: 809 else:
803 tab_bar_h = _elts.item(0).offsetHeight 810 tab_bar_h = _elts.item(0).offsetHeight
804 ideal_height = Window.getClientHeight() - tab_bar_h 811 ideal_height = Window.getClientHeight() - tab_bar_h
805 self.setHeight("%s%s" % (ideal_height, "px")); 812 self.setHeight("%s%s" % (ideal_height, "px"))
806