comparison browser_side/panels.py @ 323:0b7934e75e76

misc: reorganization of the file panels.py + clean the modules import: - some existing modules were not found during JS runtime (panels.py was too large?) - the *Panel classes of panels.py that do not reference "host" have been moved to base_panels.py - cleaned the import in various files
author souliane <souliane@mailoo.org>
date Sat, 04 Jan 2014 00:17:46 +0100
parents 971e3812903a
children 36927be51481
comparison
equal deleted inserted replaced
322:971e3812903a 323:0b7934e75e76
29 from pyjamas.ui.TextArea import TextArea 29 from pyjamas.ui.TextArea import TextArea
30 from pyjamas.ui.Label import Label 30 from pyjamas.ui.Label import Label
31 from pyjamas.ui.Button import Button 31 from pyjamas.ui.Button import Button
32 from pyjamas.ui.HTML import HTML 32 from pyjamas.ui.HTML import HTML
33 from pyjamas.ui.Image import Image 33 from pyjamas.ui.Image import Image
34 from pyjamas.ui.PopupPanel import PopupPanel
35 from pyjamas.ui.StackPanel import StackPanel
36 from pyjamas.ui.ClickListener import ClickHandler 34 from pyjamas.ui.ClickListener import ClickHandler
37 from pyjamas.ui.FlowPanel import FlowPanel 35 from pyjamas.ui.FlowPanel import FlowPanel
38 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler 36 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler
39 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
40 from pyjamas.ui.MouseListener import MouseHandler 37 from pyjamas.ui.MouseListener import MouseHandler
38 from pyjamas.ui.FocusListener import FocusHandler
41 from pyjamas.Timer import Timer 39 from pyjamas.Timer import Timer
42 from pyjamas import DOM 40 from pyjamas import DOM
41 from pyjamas import Window
42 from __pyjamas__ import doc
43
44 from tools import html_sanitize, setPresenceStyle
45 from base_panels import ChatText, OccupantsList, PopupMenuPanel
46 from datetime import datetime
47 from time import time
43 from card_game import CardPanel 48 from card_game import CardPanel
44 from radiocol import RadioColPanel 49 from radiocol import RadioColPanel
45 from menu import Menu 50 from menu import Menu
46 from jid import JID 51 from jid import JID
47 from tools import html_sanitize, inlineRoot, setPresenceStyle
48 from sat_frontends.tools.strings import addURLToText
49 from datetime import datetime
50 from time import time
51 import dialog 52 import dialog
52 import base_widget 53 import base_widget
53 from dialog import ConfirmDialog
54 import richtext 54 import richtext
55
56 from constants import Const
55 from plugin_xep_0085 import ChatStateMachine 57 from plugin_xep_0085 import ChatStateMachine
56 from pyjamas import Window
57 from __pyjamas__ import doc
58 from sat_frontends.tools.games import SYMBOLS 58 from sat_frontends.tools.games import SYMBOLS
59 from sat_frontends import constants 59 from sat_frontends.tools.strings import addURLToText
60 from pyjamas.ui.FocusListener import FocusHandler 60
61 import logging 61 import logging
62
63
64 const = constants.Const # to directly import 'const' doesn't work
65 62
66 63
67 class UniBoxPanel(HorizontalPanel): 64 class UniBoxPanel(HorizontalPanel):
68 """Panel containing the UniBox""" 65 """Panel containing the UniBox"""
69 66
423 """Actually set the entry content (header, icons, bubble...)""" 420 """Actually set the entry content (header, icons, bubble...)"""
424 self.delete_label = self.update_label = self.comment_label = None 421 self.delete_label = self.update_label = self.comment_label = None
425 self.bubble = self.editbox = self._current_comment = None 422 self.bubble = self.editbox = self._current_comment = None
426 self._setHeader() 423 self._setHeader()
427 if self.empty: 424 if self.empty:
428 self.editable_content = ['', const.SYNTAX_XHTML] 425 self.editable_content = ['', Const.SYNTAX_XHTML]
429 else: 426 else:
430 self.editable_content = [self.xhtml, const.SYNTAX_XHTML] if self.xhtml else [self.content, None] 427 self.editable_content = [self.xhtml, Const.SYNTAX_XHTML] if self.xhtml else [self.content, None]
431 self.setEntryDialog() 428 self.setEntryDialog()
432 self._setIcons() 429 self._setIcons()
433 430
434 def _setHeader(self): 431 def _setHeader(self):
435 """Set the entry header""" 432 """Set the entry header"""
587 def confirm_cb(answer): 584 def confirm_cb(answer):
588 if answer: 585 if answer:
589 self._blog_panel.host.bridge.call('deleteMblog', None, self.pub_data, self.comments) 586 self._blog_panel.host.bridge.call('deleteMblog', None, self.pub_data, self.comments)
590 587
591 target = 'message and all its comments' if self.comments else 'comment' 588 target = 'message and all its comments' if self.comments else 'comment'
592 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to delete this %s?" % target) 589 _dialog = dialog.ConfirmDialog(confirm_cb, text="Do you really want to delete this %s?" % target)
593 _dialog.show() 590 _dialog.show()
594 591
595 def _comment(self): 592 def _comment(self):
596 """Add an empty entry for a new comment""" 593 """Add an empty entry for a new comment"""
597 if self._current_comment: 594 if self._current_comment:
968 self.presence_button = Label(u"◉") 965 self.presence_button = Label(u"◉")
969 self.presence_button.setStyleName("presence-button") 966 self.presence_button.setStyleName("presence-button")
970 self.status_panel = StatusPanel(host, status=status) 967 self.status_panel = StatusPanel(host, status=status)
971 self.setPresence(presence) 968 self.setPresence(presence)
972 entries = {} 969 entries = {}
973 for value in const.PRESENCE.keys(): 970 for value in Const.PRESENCE.keys():
974 entries.update({const.PRESENCE[value]: {"value": value}}) 971 entries.update({Const.PRESENCE[value]: {"value": value}})
975 972
976 def callback(sender, key): 973 def callback(sender, key):
977 self.setPresence(entries[key]["value"]) # order matters 974 self.setPresence(entries[key]["value"]) # order matters
978 self.host.send([("STATUS", None)], self.status_panel.status) 975 self.host.send([("STATUS", None)], self.status_panel.status)
979 976
994 def getPresence(self): 991 def getPresence(self):
995 return self.presence 992 return self.presence
996 993
997 def setPresence(self, presence): 994 def setPresence(self, presence):
998 status = self.status_panel.status 995 status = self.status_panel.status
999 if not status.strip() or status == "&nbsp;" or (self.presence in const.PRESENCE and status == const.PRESENCE[self.presence]): 996 if not status.strip() or status == "&nbsp;" or (self.presence in Const.PRESENCE and status == Const.PRESENCE[self.presence]):
1000 self.changeStatus(const.PRESENCE[presence]) 997 self.changeStatus(Const.PRESENCE[presence])
1001 self.presence = presence 998 self.presence = presence
1002 setPresenceStyle(self.presence_button, self.presence) 999 setPresenceStyle(self.presence_button, self.presence)
1003 1000
1004 def changeStatus(self, new_status): 1001 def changeStatus(self, new_status):
1005 self.status_panel.changeStatus(new_status) 1002 self.status_panel.changeStatus(new_status)
1006 1003
1007 def onClick(self, sender): 1004 def onClick(self, sender):
1008 # As status is the default target of uniBar, we don't want to select anything if click on it 1005 # As status is the default target of uniBar, we don't want to select anything if click on it
1009 self.host.setSelected(None) 1006 self.host.setSelected(None)
1010
1011
1012 class ChatText(HTMLPanel):
1013
1014 def __init__(self, timestamp, nick, mymess, msg, xhtml = None):
1015 _date = datetime.fromtimestamp(float(timestamp or time()))
1016 _msg_class = ["chat_text_msg"]
1017 if mymess:
1018 _msg_class.append("chat_text_mymess")
1019 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>" %
1020 {"timestamp": _date.strftime("%H:%M"),
1021 "nick": "[%s]" % html_sanitize(nick),
1022 "msg_class": ' '.join(_msg_class),
1023 "msg": addURLToText(html_sanitize(msg)) if not xhtml else inlineRoot(xhtml)} #FIXME: images and external links must be removed according to preferences
1024 )
1025 self.setStyleName('chatText')
1026
1027
1028 class Occupant(HTML):
1029 """Occupant of a MUC room"""
1030
1031 def __init__(self, nick, special=""):
1032 HTML.__init__(self)
1033 self.nick = nick
1034 self.special = special
1035 self._refresh()
1036
1037 def __str__(self):
1038 return self.nick
1039
1040 def addSpecial(self, special=""):
1041 if special not in self.special:
1042 self.special += special
1043 self._refresh()
1044
1045 def _refresh(self):
1046 special = "" if len(self.special) == 0 else " %s" % self.special
1047 self.setHTML("<div class='occupant'>%s%s</div>" % (html_sanitize(self.nick), special))
1048
1049
1050 class OccupantsList(AbsolutePanel):
1051 """Panel user to show occupants of a room"""
1052
1053 def __init__(self):
1054 AbsolutePanel.__init__(self)
1055 self.occupants_list = {}
1056 self.setStyleName('occupantsList')
1057
1058 def addOccupant(self, nick):
1059 _occupant = Occupant(nick)
1060 self.occupants_list[nick] = _occupant
1061 self.add(_occupant)
1062
1063 def removeOccupant(self, nick):
1064 try:
1065 self.remove(self.occupants_list[nick])
1066 except KeyError:
1067 print "ERROR: trying to remove an unexisting nick"
1068
1069 def clear(self):
1070 self.occupants_list.clear()
1071 AbsolutePanel.clear(self)
1072
1073 def addSpecials(self, occupants=[], html=""):
1074 index = 0
1075 special = html
1076 for occupant in occupants:
1077 if occupant in self.occupants_list.keys():
1078 if isinstance(html, list):
1079 special = html[index]
1080 index = (index + 1) % len(html)
1081 self.occupants_list[occupant].addSpecial(special)
1082 1007
1083 1008
1084 class ChatPanel(base_widget.LiberviaWidget): 1009 class ChatPanel(base_widget.LiberviaWidget):
1085 1010
1086 def __init__(self, host, target, type_='one2one'): 1011 def __init__(self, host, target, type_='one2one'):
1388 @param enable: boolean 1313 @param enable: boolean
1389 @return: UniBox instance or None if disabled 1314 @return: UniBox instance or None if disabled
1390 """ 1315 """
1391 self.unibox_panel.setVisible(enable) 1316 self.unibox_panel.setVisible(enable)
1392 return self.unibox_panel.setUniBox(enable) 1317 return self.unibox_panel.setUniBox(enable)
1393
1394
1395 class PopupMenuPanel(PopupPanel):
1396 """This implementation of a popup menu (context menu) allow you to assign
1397 two special methods which are common to all the items, in order to hide
1398 certain items and also easily define their callbacks. The menu can be
1399 bound to any of the mouse button (left, middle, right).
1400 """
1401 def __init__(self, entries, hide=None, callback=None, vertical=True, style={}, **kwargs):
1402 """
1403 @param entries: a dict of dicts, where each sub-dict is representing
1404 one menu item: the sub-dict key can be used as the item text and
1405 description, but optional "title" and "desc" entries would be used
1406 if they exists. The sub-dicts may be extended later to do
1407 more complicated stuff or overwrite the common methods.
1408 @param hide: function with 2 args: widget, key as string and
1409 returns True if that item should be hidden from the context menu.
1410 @param callback: function with 2 args: sender, key as string
1411 @param vertical: True or False, to set the direction
1412 @param item_style: alternative CSS class for the menu items
1413 @param menu_style: supplementary CSS class for the sender widget
1414 """
1415 PopupPanel.__init__(self, autoHide=True, **kwargs)
1416 self._entries = entries
1417 self._hide = hide
1418 self._callback = callback
1419 self.vertical = vertical
1420 self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"}
1421 self.style.update(style)
1422 self._senders = {}
1423
1424 def _show(self, sender):
1425 """Popup the menu relative to this sender's position.
1426 @param sender: the widget that has been clicked
1427 """
1428 menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
1429 menu.setStyleName(self.style["menu"])
1430
1431 def button_cb(item):
1432 """You can not put that method in the loop and rely
1433 on _key, because it is overwritten by each step.
1434 You can rely on item.key instead, which is copied
1435 from _key after the item creation.
1436 @param item: the menu item that has been clicked
1437 """
1438 if self._callback is not None:
1439 self._callback(sender=sender, key=item.key)
1440 self.hide(autoClosed=True)
1441
1442 for _key in self._entries.keys():
1443 entry = self._entries[_key]
1444 if self._hide is not None and self._hide(sender=sender, key=_key) is True:
1445 continue
1446 title = entry["title"] if "title" in entry.keys() else _key
1447 item = Button(title, button_cb)
1448 item.key = _key
1449 item.setStyleName(self.style["item"])
1450 item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
1451 menu.add(item)
1452 if len(menu.getChildren()) == 0:
1453 return
1454 self.add(menu)
1455 if self.vertical is True:
1456 x = sender.getAbsoluteLeft() + sender.getOffsetWidth()
1457 y = sender.getAbsoluteTop()
1458 else:
1459 x = sender.getAbsoluteLeft()
1460 y = sender.getAbsoluteTop() + sender.getOffsetHeight()
1461 self.setPopupPosition(x, y)
1462 self.show()
1463 if self.style["selected"]:
1464 sender.addStyleDependentName(self.style["selected"])
1465
1466 def _onHide(popup):
1467 if self.style["selected"]:
1468 sender.removeStyleDependentName(self.style["selected"])
1469 return PopupPanel.onHideImpl(self, popup)
1470
1471 self.onHideImpl = _onHide
1472
1473 def registerClickSender(self, sender, button=BUTTON_LEFT):
1474 """Bind the menu to the specified sender.
1475 @param sender: the widget to which the menu should be bound
1476 @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT
1477 """
1478 self._senders.setdefault(sender, [])
1479 self._senders[sender].append(button)
1480
1481 if button == BUTTON_RIGHT:
1482 # WARNING: to disable the context menu is a bit tricky...
1483 # The following seems to work on Firefox 24.0, but:
1484 # TODO: find a cleaner way to disable the context menu
1485 sender.getElement().setAttribute("oncontextmenu", "return false")
1486
1487 def _onBrowserEvent(event):
1488 button = DOM.eventGetButton(event)
1489 if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]:
1490 self._show(sender)
1491 return sender.__class__.onBrowserEvent(sender, event)
1492
1493 sender.onBrowserEvent = _onBrowserEvent
1494
1495 def registerMiddleClickSender(self, sender):
1496 self.registerClickSender(sender, BUTTON_MIDDLE)
1497
1498 def registerRightClickSender(self, sender):
1499 self.registerClickSender(sender, BUTTON_RIGHT)
1500
1501
1502 class ToggleStackPanel(StackPanel):
1503 """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be
1504 visible at the same time, clicking a sub-panel header will not display it and hide
1505 the others but only toggle its own visibility. The argument 'visibleStack' is ignored.
1506 Note that the argument 'visible' has been added to listener's 'onStackChanged' method.
1507 """
1508
1509 def __init__(self, **kwargs):
1510 StackPanel.__init__(self, **kwargs)
1511
1512 def onBrowserEvent(self, event):
1513 if DOM.eventGetType(event) == "click":
1514 index = self.getDividerIndex(DOM.eventGetTarget(event))
1515 if index != -1:
1516 self.toggleStack(index)
1517
1518 def add(self, widget, stackText="", asHTML=False, visible=False):
1519 StackPanel.add(self, widget, stackText, asHTML)
1520 self.setStackVisible(self.getWidgetCount() - 1, visible)
1521
1522 def toggleStack(self, index):
1523 if index >= self.getWidgetCount():
1524 return
1525 visible = not self.getWidget(index).getVisible()
1526 self.setStackVisible(index, visible)
1527 for listener in self.stackListeners:
1528 listener.onStackChanged(self, index, visible)