comparison browser_side/panels.py @ 33:e70521e6d803

browser side, misc stuffs - dropCell are now keep the dragover style if the mouse go on an other widget inside them - uniBox moved to panels.py - chatPanel now partially managed MUC Room - room joined open in a new tab (as Tarot games) - MainPanel & MainTabPanel are now dynamically sized in pixels - fixed stupid bug in json callbacks management
author Goffi <goffi@goffi.org>
date Sat, 14 May 2011 01:11:08 +0200
parents d89982865c57
children ed935f763cc8
comparison
equal deleted inserted replaced
32:6b8da70b0799 33:e70521e6d803
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 import pyjd # this is dummy in pyjs 22 import pyjd # this is dummy in pyjs
23 from pyjamas.ui.SimplePanel import SimplePanel 23 from pyjamas.ui.SimplePanel import SimplePanel
24 from pyjamas.ui.FlowPanel import FlowPanel
25 from pyjamas.ui.AbsolutePanel import AbsolutePanel
24 from pyjamas.ui.VerticalPanel import VerticalPanel 26 from pyjamas.ui.VerticalPanel import VerticalPanel
25 from pyjamas.ui.HorizontalPanel import HorizontalPanel 27 from pyjamas.ui.HorizontalPanel import HorizontalPanel
26 from pyjamas.ui.ScrollPanel import ScrollPanel 28 from pyjamas.ui.ScrollPanel import ScrollPanel
27 from pyjamas.ui.TabPanel import TabPanel 29 from pyjamas.ui.TabPanel import TabPanel
28 from pyjamas.ui.HTMLPanel import HTMLPanel 30 from pyjamas.ui.HTMLPanel import HTMLPanel
31 from pyjamas.ui.PopupPanel import PopupPanel
29 from pyjamas.ui.Grid import Grid 32 from pyjamas.ui.Grid import Grid
33 from pyjamas.ui.AutoComplete import AutoCompleteTextBox
30 from pyjamas.ui.MenuBar import MenuBar 34 from pyjamas.ui.MenuBar import MenuBar
31 from pyjamas.ui.MenuItem import MenuItem 35 from pyjamas.ui.MenuItem import MenuItem
32 from pyjamas.ui.Label import Label 36 from pyjamas.ui.Label import Label
37 from pyjamas.ui.HTML import HTML
33 from pyjamas.ui.DropWidget import DropWidget 38 from pyjamas.ui.DropWidget import DropWidget
34 from pyjamas.ui.ClickListener import ClickHandler 39 from pyjamas.ui.ClickListener import ClickHandler
35 from pyjamas.ui import HasAlignment 40 from pyjamas.ui import HasAlignment
41 from pyjamas.ui.KeyboardListener import KEY_ENTER
42 from pyjamas.Timer import Timer
36 from pyjamas import Window 43 from pyjamas import Window
37 from pyjamas import DOM 44 from pyjamas import DOM
45 from __pyjamas__ import JS, doc
38 46
39 from pyjamas.dnd import makeDraggable 47 from pyjamas.dnd import makeDraggable
40 from pyjamas.ui.DragWidget import DragWidget, DragContainer 48 from pyjamas.ui.DragWidget import DragWidget, DragContainer
41 from jid import JID 49 from jid import JID
50 from tools import html_sanitize
42 from datetime import datetime 51 from datetime import datetime
43 from time import time 52 from time import time
44 from dialog import ContactsChooser 53 from dialog import ContactsChooser
45 54
46 class MenuCmd: 55 class MenuCmd:
94 print "drag enter" 103 print "drag enter"
95 self.addStyleName('dragover') 104 self.addStyleName('dragover')
96 DOM.eventPreventDefault(event) 105 DOM.eventPreventDefault(event)
97 106
98 def onDragLeave(self, event): 107 def onDragLeave(self, event):
99 print "drag leave" 108 print "\ndrag leave"
100 self.removeStyleName('dragover') 109 if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop() or\
110 event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth()-1 or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight()-1:
111 #We check that we are inside widget's box, and we don't remove the style in this case because
112 #if the mouse is over a widget inside the DropWidget, if will leave the DropWidget, and we
113 #don't want that
114 self.removeStyleName('dragover')
101 115
102 def onDragOver(self, event): 116 def onDragOver(self, event):
103 DOM.eventPreventDefault(event) 117 DOM.eventPreventDefault(event)
104 118
105 def _getCellAndRow(self, grid, event): 119 def _getCellAndRow(self, grid, event):
148 def __init__(self, host): 162 def __init__(self, host):
149 SimplePanel.__init__(self) 163 SimplePanel.__init__(self)
150 self.host = host 164 self.host = host
151 _panel = HTMLPanel("&nbsp;") 165 _panel = HTMLPanel("&nbsp;")
152 self.add(_panel) 166 self.add(_panel)
153 self.setHeight('100%') 167 self.setHeight('50%')
154 DropCell.__init__(self) 168 DropCell.__init__(self)
155 169
170 class UniBoxPanel(SimplePanel):
171 """Panel containing the UniBox"""
172
173 def __init__(self, host):
174 SimplePanel.__init__(self)
175 self.setStyleName('uniBoxPanel')
176 self.unibox = UniBox(host)
177 self.unibox.setWidth('100%')
178 self.add(self.unibox)
179
180 class UniBox(AutoCompleteTextBox):
181 """This text box is used as a main typing point, for message, microblog, etc"""
182
183 def __init__(self, host):
184 AutoCompleteTextBox.__init__(self)
185 self._popup = None
186 self._timer = Timer(notify=self._timeCb)
187 self.host = host
188
189 def addKey(self, key):
190 self.getCompletionItems().completions.append(key)
191
192 def showWarning(self, target_data):
193 type, target = target_data
194 if type == "PUBLIC":
195 msg = "This message will be PUBLIC and everybody will be able to see it, even people you don't know"
196 style = "targetPublic"
197 elif type == "GROUP":
198 msg = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" % (target or '')
199 style = "targetGroup"
200 elif type == "STATUS":
201 msg = "This will be your new status message"
202 style = "targetStatus"
203 elif type == "ONE2ONE":
204 msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % target
205 style = "targetOne2One"
206 else:
207 print "WARNING: undetermined target for this message"
208 return
209 contents = HTML(msg)
210
211 self._popup = PopupPanel(autoHide=False, modal=False)
212 self._popup.target_data = target_data
213 self._popup.add(contents)
214 self._popup.setStyleName("warningPopup")
215 if style:
216 self._popup.addStyleName(style)
217
218 left = 0
219 top = 0 #max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2)
220 self._popup.setPopupPosition(left, top)
221 self._popup.setPopupPosition(left, top)
222 self._popup.show()
223
224 def _timeCb(self, timer):
225 if self._popup:
226 self._popup.hide()
227 del self._popup
228 self._popup = None
229
230 def _getTarget(self, txt):
231 """Say who will receive the messsage
232 Return a tuple (target_type, target info)"""
233 type = None
234 target = None
235 if txt.startswith('@@: '):
236 type = "PUBLIC"
237 elif txt.startswith('@'):
238 type = "GROUP"
239 _end = txt.find(': ')
240 if _end == -1:
241 type = "STATUS"
242 else:
243 target = txt[1:_end] #only one target group is managed for the moment
244 if not target in self.host.contact_panel.getGroups():
245 target = None
246 elif self.host.selected == None:
247 type = "STATUS"
248 elif isinstance(self.host.selected, ChatPanel):
249 type = "ONE2ONE"
250 target = str(self.host.selected.target)
251 else:
252 print self.host.selected
253 type = "UNKNOWN"
254 return (type, target)
255
256 def onKeyPress(self, sender, keycode, modifiers):
257 _txt = self.getText()
258 if not self._popup:
259 self.showWarning(self._getTarget(_txt))
260 else:
261 _target = self._getTarget(_txt)
262 if _target != self._popup.target_data:
263 self._timeCb(None) #we remove the popup
264 self.showWarning(_target)
265
266 self._timer.schedule(2000)
267
268 if keycode == KEY_ENTER and not self.visible:
269 if _txt:
270 if _txt.startswith('@'):
271 self.host.bridge.call('sendMblog', None, self.getText())
272 elif self.host.selected == None:
273 self.host.bridge.call('setStatus', None, _txt)
274 elif isinstance(self.host.selected, ChatPanel):
275 _chat = self.host.selected
276 mess_type = "groupchat" if _chat.type=='group' else "chat"
277 self.host.bridge.call('sendMessage', None, str(_chat.target), _txt, '', mess_type)
278 self.setText('')
279 self._timeCb(None) #we remove the popup
280
281 def complete(self):
282 #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done
283 #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this
284 return AutoCompleteTextBox.complete(self)
285
156 class MicroblogEntry(SimplePanel): 286 class MicroblogEntry(SimplePanel):
157 287
158 def __init__(self, body, author, timestamp): 288 def __init__(self, body, author, timestamp):
159 SimplePanel.__init__(self) 289 SimplePanel.__init__(self)
160 290
183 _class.append('empty_header') 313 _class.append('empty_header')
184 ScrollPanel.__init__(self) 314 ScrollPanel.__init__(self)
185 self.vpanel = VerticalPanel() 315 self.vpanel = VerticalPanel()
186 self.vpanel.add(HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title))) 316 self.vpanel.add(HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title)))
187 self.vpanel.setWidth('100%') 317 self.vpanel.setWidth('100%')
188 self.setHeight('100%') 318 self.setHeight('50%')
189 self.setStyleName('microblogPanel') 319 self.setStyleName('microblogPanel')
190 self.add(self.vpanel) 320 self.add(self.vpanel)
191 DropCell.__init__(self) 321 DropCell.__init__(self)
192 322
193 def addEntry(self, text, author=None, timestamp=None): 323 def addEntry(self, text, author=None, timestamp=None):
250 "nick": "[%s]" % nick, 380 "nick": "[%s]" % nick,
251 "msg_class": ' '.join(_msg_class), 381 "msg_class": ' '.join(_msg_class),
252 "msg": msg} 382 "msg": msg}
253 ) 383 )
254 self.setStyleName('chatText') 384 self.setStyleName('chatText')
255 385
256 class ChatPanel(DropCell, ClickHandler, ScrollPanel): 386 class Occupant(HTML):
387 """Occupant of a MUC room"""
388
389 def __init__(self, nick):
390 self.nick = nick
391 HTML.__init__(self, "<div class='occupant'>%s</div>" % html_sanitize(nick))
392
393 class OccupantsList(AbsolutePanel):
394 """Panel user to show occupants of a room"""
395
396 def __init__(self):
397 AbsolutePanel.__init__(self)
398 self.setStyleName('occupantsList')
399 self.occupants = set()
400 #self.setHeight('100%')
401
402 def addOccupant(self, nick):
403 _occupant = Occupant(nick)
404 print "addOccupant: nick", nick
405 print _occupant
406 self.occupants.add(_occupant)
407 self.add(_occupant)
408
409
410
411
412 class ChatPanel(DropCell, ClickHandler, AbsolutePanel):
257 413
258 def __init__(self, host, target, type='one2one'): 414 def __init__(self, host, target, type='one2one'):
259 """Panel used for conversation (one 2 one or group chat) 415 """Panel used for conversation (one 2 one or group chat)
260 @param host: SatWebFrontend instance 416 @param host: SatWebFrontend instance
261 @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room) 417 @param target: entity (JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room)
262 @param type: one2one for simple conversation, group for MUC""" 418 @param type: one2one for simple conversation, group for MUC"""
263 self.host = host 419 self.host = host
420 self.type = type
421 self.nick = None
264 if not target: 422 if not target:
265 print "ERROR: Empty target !" 423 print "ERROR: Empty target !"
266 return 424 return
267 self.target = target 425 self.target = target
268 title="%s" % target.bare 426 title="%s" % target.bare
269 title.replace('<','&lt;').replace('>','&gt;') 427 title.replace('<','&lt;').replace('>','&gt;')
270 _class = ['mb_panel_header'] 428 _class = ['mb_panel_header']
271 ScrollPanel.__init__(self) 429 AbsolutePanel.__init__(self)
272 self.content = VerticalPanel() 430 self.header = HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title))
273 self.content.add(HTMLPanel("<div class='%s'>%s</div>" % (','.join(_class),title))) 431 self.body = AbsolutePanel()
274 self.content.setWidth('100%') 432 self.body.setStyleName('chatPanel_body')
275 self.setHeight('100%') 433 #self.body.setWidth('100%')
434 #self.body.setHeight('50%')
435 chat_area = HorizontalPanel()
436 chat_area.setStyleName('chatArea')
437 #chat_area.setHeight('50%')
438 if type == 'group':
439 self.occupants_list = OccupantsList()
440 chat_area.add(self.occupants_list)
441 self.body.add(chat_area)
442 self.content = AbsolutePanel()
443 self.content.setStyleName('chatContent')
444 #_scrollp.setHeight('50%')
445 #_scrollp.setWidth('100%')
446 chat_area.add(ScrollPanel(self.content, Size=("100%", "100%")))
447 self.add(self.header)
448 self.add(self.body)
449 #self.content.setWidth('100%')
450 #self.main_panel.setVerticalAlignment(HasAlignment.ALIGN_TOP)
451 #self.body.setVerticalAlignment(HasAlignment.ALIGN_TOP)
452 #chat_area.setVerticalAlignment(HasAlignment.ALIGN_TOP)
453 #self.setWidth('100%')
454 #self.setHeight('50%')
276 self.setStyleName('chatPanel') 455 self.setStyleName('chatPanel')
277 self.add(self.content)
278 DropCell.__init__(self) 456 DropCell.__init__(self)
279 ClickHandler.__init__(self) 457 ClickHandler.__init__(self)
280 self.addClickListener(self) 458 self.addClickListener(self)
281 459
282 def onClick(self, sender, event): 460 def onClick(self, sender, event):
283 self.host.select(self) 461 self.host.select(self)
284 462
285 def setUserNick(self, nick): 463 def setUserNick(self, nick):
286 """Set the nick of the user, usefull for e.g. change the color of the user""" 464 """Set the nick of the user, usefull for e.g. change the color of the user"""
287 self.nick = nick 465 self.nick = nick
288 466
467 def setPresents(self, nicks):
468 """Set the users presents in this room
469 @param occupants: list of nicks (string)"""
470 for nick in nicks:
471 self.occupants_list.addOccupant(nick)
472
289 def historyPrint(self, size=20): 473 def historyPrint(self, size=20):
290 """Print the initial history""" 474 """Print the initial history"""
291 def getHistoryCB(history): 475 def getHistoryCB(history):
292 stamps=history.keys() 476 stamps=history.keys()
293 stamps.sort() 477 stamps.sort()
294 for stamp in stamps: 478 for stamp in stamps:
295 self.printMessage(history[stamp][0], history[stamp][1], stamp) 479 self.printMessage(history[stamp][0], history[stamp][1], stamp)
296 self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, str(self.target), 20) 480 self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, str(self.target), 20)
297 481
298 def printMessage(self, from_jid, msg, timestamp=None): 482 def printMessage(self, from_jid, msg, timestamp=None):
299 """Print message in chat window. Must be implemented by child class""" 483 """Print message in chat window. Must be implemented by child class"""
300 _jid=JID(from_jid) 484 _jid=JID(from_jid)
301 nick = _jid.node 485 nick = _jid.node if self.type=='one2one' else _jid.resource
302 mymess = _jid.bare == self.host.whoami.bare #mymess = True if message comes from local user 486 mymess = _jid.resource == self.nick if self.type == "group" else _jid.bare == self.host.whoami.bare #mymess = True if message comes from local user
303 """if msg.startswith('/me '): 487 """if msg.startswith('/me '):
304 self.printInfo('* %s %s' % (nick, msg[4:]),type='me') 488 self.printInfo('* %s %s' % (nick, msg[4:]),type='me')
305 return""" 489 return"""
306 self.content.add(ChatText(timestamp, nick, mymess, msg)) 490 self.content.add(ChatText(timestamp, nick, mymess, msg))
307 491
311 self.host=host 495 self.host=host
312 HorizontalPanel.__init__(self) 496 HorizontalPanel.__init__(self)
313 self._left = self.host.contact_panel 497 self._left = self.host.contact_panel
314 self._right = Grid(1,3) 498 self._right = Grid(1,3)
315 self._right.setWidth('100%') 499 self._right.setWidth('100%')
316 self._right.setHeight('100%') 500 self._right.setHeight('50%')
317 self.add(self._left) 501 self.add(self._left)
318 self.setCellWidth(self._left, "15%") 502 self.setCellWidth(self._left, "15%")
319 self.add(self._right) 503 self.add(self._right)
320 self.setCellWidth(self._right, "85%") 504 self.setCellWidth(self._right, "85%")
321 self.setHeight('100%') 505 self.setHeight('50%')
322 506
323 def changePanel(self, idx, panel): 507 def changePanel(self, idx, panel):
324 self._right.setWidget(0,idx,panel) 508 self._right.setWidget(0,idx,panel)
325 509
326 class MainTabPannel(TabPanel): 510 class MainTabPannel(TabPanel):
327 511
328 def __init__(self, host): 512 def __init__(self, host):
513 TabPanel.__init__(self)
329 self.host=host 514 self.host=host
330 TabPanel.__init__(self) 515 TabPanel()
331 self.tabBar.setVisible(False) 516 self.tabBar.setVisible(False)
517 self.addStyleName('mainTabPanel')
518 Window.addWindowResizeListener(self)
519
520 def onWindowResized(self, width, height):
521 print "onWindowResized"
522 tab_panel_elt = self.getElement()
523 _elts = doc().getElementsByClassName('gwt-TabBar')
524 if not _elts.length:
525 print ("ERROR: not TabBar found, it should exist !")
526 tab_bar_h = 0
527 else:
528 tab_bar_h = _elts.item(0).offsetHeight
529 ideal_height = Window.getClientHeight() - tab_panel_elt.offsetTop - tab_bar_h - 5
530 print "ideal_height =",ideal_height
531 self.setWidth("%s%s" % (width-5, "px"));
532 self.setHeight("%s%s" % (ideal_height, "px"));
332 533
333 def add(self, widget, tabText=None, asHTML=False): 534 def add(self, widget, tabText=None, asHTML=False):
334 TabPanel.add(self, widget, tabText, asHTML) 535 TabPanel.add(self, widget, tabText, asHTML)
335 if self.getWidgetCount()>1: 536 if self.getWidgetCount()>1:
336 self.tabBar.setVisible(True) 537 self.tabBar.setVisible(True)
538 self.host.resize()
337 539
338 class MainPanel(VerticalPanel): 540 """class MainTabPannel(FlowPanel):
339 541
340 def __init__(self, host): 542 def __init__(self, host):
543 FlowPanel.__init__(self)
341 self.host=host 544 self.host=host
342 VerticalPanel.__init__(self) 545 self.tab_panel = TabPanel()
343 546 self.tab_panel.tabBar.setVisible(False)
344 self.setHorizontalAlignment(HasAlignment.ALIGN_LEFT) 547 self.addStyleName('mainTabPanel')
345 self.setVerticalAlignment(HasAlignment.ALIGN_TOP) 548 FlowPanel.add(self, self.tab_panel)
346
347 menu = Menu(host)
348 uni_box = host.uni_box
349 status = host.status_panel
350 self.tab_panel = MainTabPannel(self)
351 self.tab_panel.setWidth('100%') 549 self.tab_panel.setWidth('100%')
352 self.tab_panel.setHeight('100%') 550 self.tab_panel.setHeight('100%')
551 Window.addWindowResizeListener(self)
552
553 def onWindowResized(self, width, height):
554 print "onWindowResized"
555 tab_panel_elt = self.getElement()
556 _elts = doc().getElementsByClassName('gwt-TabBar')
557 if not _elts.length:
558 print ("ERROR: not TabBar found, it should exist !")
559 tab_bar_h = 0
560 else:
561 tab_bar_h = _elts.item(0).offsetHeight
562 ideal_height = Window.getClientHeight() - tab_panel_elt.offsetTop - tab_bar_h - 5
563 print "ideal_height =",ideal_height
564 self.setWidth("%s%s" % (width-5, "px"));
565 self.setHeight("%s%s" % (ideal_height, "px"));
566
567 def selectTab(self, idx):
568 self.tab_panel.selectTab(idx)
569
570 def add(self, widget, tabText=None, asHTML=False):
571 self.tab_panel.add(widget, tabText, asHTML)
572 if self.tab_panel.getWidgetCount()>1:
573 self.tab_panel.tabBar.setVisible(True)
574 self.host.resize()"""
575
576 class MainPanel(AbsolutePanel):
577
578 def __init__(self, host):
579 self.host=host
580 AbsolutePanel.__init__(self)
581
582 #self.setHorizontalAlignment(HasAlignment.ALIGN_LEFT)
583 #self.setVerticalAlignment(HasAlignment.ALIGN_TOP)
584
585 menu = Menu(host)
586 unibox_panel = UniBoxPanel(host)
587 self.host.setUniBox(unibox_panel.unibox)
588 status = host.status_panel
589 self.tab_panel = MainTabPannel(host)
590 #self.tab_panel.setWidth('100%')
591 #self.tab_panel.setHeight('50%')
353 self.discuss_panel = MainDiscussionPannel(self.host) 592 self.discuss_panel = MainDiscussionPannel(self.host)
354 self.discuss_panel.setWidth('100%') 593 self.discuss_panel.setWidth('100%')
355 self.discuss_panel.setHeight('100%') 594 self.discuss_panel.setHeight('50%')
356 self.tab_panel.add(self.discuss_panel, "Discussions") 595 self.tab_panel.add(self.discuss_panel, "Discussions")
357 self.tab_panel.selectTab(0) 596 self.tab_panel.selectTab(0)
358 597
359 self.add(menu) 598 self.add(menu)
360 self.add(uni_box) 599 self.add(unibox_panel)
361 self.add(status) 600 self.add(status)
362 self.add(self.tab_panel) 601 self.add(self.tab_panel)
602 #self.tab_panel.onWindowResized(Window.getClientWidth(), Window.getClientHeight())
363 603
364 self.setCellHeight(menu, "5%") 604 """self.setCellHeight(menu, "5%")
365 self.setCellHeight(uni_box, "5%") 605 self.setCellHeight(uni_box, "5%")
366 self.setCellVerticalAlignment(uni_box, HasAlignment.ALIGN_CENTER) 606 self.setCellVerticalAlignment(uni_box, HasAlignment.ALIGN_CENTER)
367 self.setCellHorizontalAlignment(uni_box, HasAlignment.ALIGN_CENTER) 607 self.setCellHorizontalAlignment(uni_box, HasAlignment.ALIGN_CENTER)
368 self.setCellHeight(self.tab_panel, "90%") 608 self.setCellHeight(self.tab_panel, "90%")
369 self.setCellWidth(self.tab_panel, "100%") 609 self.setCellWidth(self.tab_panel, "100%")"""
370 610
371 self.setWidth("100%") 611 self.setWidth("100%")
372 self.setHeight("100%") 612 #self.setHeight("99%")
613 Window.addWindowResizeListener(self)
614
615 def onWindowResized(self, width, height):
616 print "resizing: %s %s" % (width, height)
617 #self.setWidth("%s%s" % (width, "px"));
618 _elts = doc().getElementsByClassName('gwt-TabBar')
619 if not _elts.length:
620 tab_bar_h = 0
621 else:
622 tab_bar_h = _elts.item(0).offsetHeight
623 ideal_height = Window.getClientHeight() - tab_bar_h
624 self.setHeight("%s%s" % (ideal_height, "px"));
625