comparison browser/sat_browser/main_panel.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/main_panel.py@f2170536ba23
children 2af117bfe6cc
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) 2011-2018 Jérôme Poisson <goffi@goffi.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 """Panels used as main basis"""
21
22 import pyjd # this is dummy in pyjs
23 from sat.core.log import getLogger
24 log = getLogger(__name__)
25
26 from sat.core.i18n import _
27 from sat_browser import strings
28
29 from pyjamas.ui.DockPanel import DockPanel
30 from pyjamas.ui.HorizontalPanel import HorizontalPanel
31 from pyjamas.ui.VerticalPanel import VerticalPanel
32 from pyjamas.ui.Button import Button
33 from pyjamas.ui.HTML import HTML
34 from pyjamas.ui.ClickListener import ClickHandler
35 from pyjamas.Timer import Timer
36 from pyjamas.ui import HasVerticalAlignment
37
38
39 import menu
40 import dialog
41 import base_widget
42 import base_menu
43 import libervia_widget
44 import editor_widget
45 import html_tools
46 from constants import Const as C
47
48
49 ### Warning notification (visibility of message, and other warning data) ###
50
51
52 class WarningPopup():
53
54 def __init__(self):
55 self._popup = None
56 self._timer = Timer(notify=self._timeCb)
57 self.timeout = None
58 self._html = None
59 self._last_type = None
60 self._last_html = None
61
62 def showWarning(self, type_=None, msg=None, duration=2000):
63 """Display a popup information message, e.g. to notify the recipient of a message being composed.
64
65 If type_ is None, a popup being currently displayed will be hidden.
66 @type_: a type determining the CSS style to be applied (see _showWarning)
67 @msg: message to be displayed
68 @duration(int, None): time (in ms) to display the message
69 """
70 if type_ is None:
71 self._removeWarning()
72 return
73
74 self.timeout = duration
75
76 if not self._popup or self._last_type != type_ or self._last_html != msg:
77 self._showWarning(type_, msg)
78
79 def _showWarning(self, type_, msg):
80 """Display a popup information message, e.g. to notify the recipient of a message being composed.
81
82 @type_: a type determining the CSS style to be applied. For now the defined styles are
83 "NONE" (will do nothing), "PUBLIC", "GROUP", "STATUS" and "ONE2ONE".
84 @msg: message to be displayed
85 """
86 if type_ == "NONE":
87 return
88 if not msg:
89 log.warning("no msg set")
90 return
91 if type_ == "PUBLIC":
92 style = "targetPublic"
93 elif type_ == "GROUP":
94 style = "targetGroup"
95 elif type_ == "STATUS":
96 style = "targetStatus"
97 elif type_ == "ONE2ONE":
98 style = "targetOne2One"
99 elif type_ == "INFO":
100 style = "notifInfo"
101 elif type_ == "WARNING":
102 style = "notifWarning"
103 else:
104 log.error("unknown message type")
105 return
106
107 self._last_html = msg
108
109 if self._popup is None:
110 self._popup = dialog.PopupPanelWrapper(autoHide=False, modal=False)
111 self._html = HTML(msg)
112 self._popup.add(self._html)
113
114 left = 0
115 top = 0 # max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2)
116 self._popup.setPopupPosition(left, top)
117 self._popup.show()
118 else:
119 self._html.setHTML(msg)
120
121 if type_ != self._last_type:
122 self._last_type = type_
123 self._popup.setStyleName("warningPopup")
124 self._popup.addStyleName(style)
125
126 if self.timeout is not None:
127 self._timer.schedule(self.timeout)
128
129 def _timeCb(self, timer):
130 if self._popup:
131 self._popup.hide()
132 self._popup = None
133
134 def _removeWarning(self):
135 """Remove the popup"""
136 self._timer.cancel()
137 self._timeCb(None)
138
139
140 ### Status ###
141
142
143 class StatusPanel(editor_widget.HTMLTextEditor):
144
145 EMPTY_STATUS = '&lt;click to set a status&gt;'
146 VALIDATE_WITH_SHIFT_ENTER = False
147
148 def __init__(self, host, status=''):
149 self.host = host
150 modifiedCb = lambda content: self.host.bridge.call('setStatus', None, self.host.presence_status_panel.presence, content['text']) or True
151 editor_widget.HTMLTextEditor.__init__(self, {'text': status}, modifiedCb, options={'no_xhtml': True, 'listen_focus': True, 'listen_click': True})
152 self.edit(False)
153 self.setStyleName('marginAuto')
154
155 @property
156 def status(self):
157 return self._original_content['text']
158
159 def __cleanContent(self, content):
160 status = content['text']
161 if status == self.EMPTY_STATUS or status in C.PRESENCE.values():
162 content['text'] = ''
163 return content
164
165 def getContent(self):
166 return self.__cleanContent(editor_widget.HTMLTextEditor.getContent(self))
167
168 def setContent(self, content):
169 content = self.__cleanContent(content)
170 editor_widget.BaseTextEditor.setContent(self, content)
171
172 def setDisplayContent(self):
173 status = self._original_content['text']
174 try:
175 presence = self.host.presence_status_panel.presence
176 except AttributeError: # during initialization
177 presence = None
178 if not status:
179 if presence and presence in C.PRESENCE:
180 status = C.PRESENCE[presence]
181 else:
182 status = self.EMPTY_STATUS
183 self.display.setHTML(strings.addURLToText(status))
184
185
186 class PresenceStatusMenuBar(base_widget.WidgetMenuBar):
187 def __init__(self, parent):
188 styles = {'menu_bar': 'presence-button'}
189 base_widget.WidgetMenuBar.__init__(self, parent, parent.host, styles=styles)
190 self.button = self.addCategory(u"◉")
191 presence_menu = self.button.getSubMenu()
192 for presence, presence_i18n in C.PRESENCE.items():
193 html = u'<span class="%s">◉</span> %s' % (html_tools.buildPresenceStyle(presence), presence_i18n)
194 presence_menu.addItem(html, True, base_menu.SimpleCmd(lambda presence=presence: self.changePresenceCb(presence)))
195 self.parent_panel = parent
196
197 def changePresenceCb(self, presence=''):
198 """Callback to notice the backend of a new presence set by the user.
199 @param presence (unicode): the new presence is a value in ('', 'chat', 'away', 'dnd', 'xa')
200 """
201 self.host.bridge.call('setStatus', None, presence, self.parent_panel.status_panel.status)
202
203 @classmethod
204 def getCategoryHTML(cls, category):
205 """Build the html to be used for displaying a category item.
206
207 @param category (quick_menus.MenuCategory): category to add
208 @return unicode: HTML to display
209 """
210 return category
211
212
213 class PresenceStatusPanel(HorizontalPanel, ClickHandler):
214
215 def __init__(self, host, presence="", status=""):
216 self.host = host
217 self.plugin_menu_context = []
218 HorizontalPanel.__init__(self, Width='100%')
219 self.presence_bar = PresenceStatusMenuBar(self)
220 self.status_panel = StatusPanel(host, status=status)
221 self.setPresence(presence)
222
223 panel = HorizontalPanel()
224 panel.add(self.presence_bar)
225 panel.add(self.status_panel)
226 panel.setCellVerticalAlignment(self.presence_bar, 'baseline')
227 panel.setCellVerticalAlignment(self.status_panel, 'baseline')
228 panel.setStyleName("presenceStatusPanel")
229 self.add(panel)
230
231 self.status_panel.edit(False)
232
233 ClickHandler.__init__(self)
234 self.addClickListener(self)
235
236 @property
237 def presence(self):
238 return self._presence
239
240 @property
241 def status(self):
242 return self.status_panel._original_content['text']
243
244 def setPresence(self, presence):
245 self._presence = presence
246 html_tools.setPresenceStyle(self.presence_bar.button, self._presence)
247
248 def setStatus(self, status):
249 self.status_panel.setContent({'text': status})
250 self.status_panel.setDisplayContent()
251
252 def onClick(self, sender):
253 # As status is the default target of uniBar, we don't want to select anything if click on it
254 self.host.setSelected(None)
255
256
257 ### Panels managing the main area ###
258
259
260 class MainPanel(DockPanel):
261 """The panel which take the whole screen"""
262
263 def __init__(self, host):
264 self.host = host
265 DockPanel.__init__(self, StyleName="mainPanel liberviaTabPanel")
266
267 # menu and status panel
268 self.header = VerticalPanel(StyleName="header")
269 self.menu = menu.MainMenuBar(host)
270 self.header.add(self.menu)
271
272 # contacts
273 self.contacts_switch = Button(u'«', self._contactsSwitch)
274 self.contacts_switch.addStyleName('contactsSwitch')
275
276 # tab panel
277 self.tab_panel = libervia_widget.MainTabPanel(host)
278 self.tab_panel.addWidgetsTab(_(u"Discussions"), select=True, locked=True)
279
280 # XXX: widget's addition order is important!
281 self.add(self.header, DockPanel.NORTH)
282 self.add(self.tab_panel, DockPanel.CENTER)
283 self.setCellWidth(self.tab_panel, '100%')
284 self.setCellHeight(self.tab_panel, '100%')
285 self.add(self.tab_panel.getTabBar(), DockPanel.SOUTH)
286
287 def addContactList(self, contact_list):
288 self.add(self.contacts_switch, DockPanel.WEST)
289 self.add(contact_list, DockPanel.WEST)
290
291 def addPresenceStatusPanel(self, panel):
292 self.header.add(panel)
293 self.header.setCellHeight(panel, '100%')
294 self.header.setCellVerticalAlignment(panel, HasVerticalAlignment.ALIGN_BOTTOM)
295
296 def _contactsSwitch(self, btn=None):
297 """ (Un)hide contacts panel """
298 if btn is None:
299 btn = self.contacts_switch
300 clist = self.host.contact_list_widget
301 clist.setVisible(not clist.getVisible())
302 btn.setText(u"«" if clist.getVisible() else u"»")
303 self.host.resize()
304
305 def _contactsMove(self, parent):
306 """Move the contacts container (containing the contact list and
307 the "hide/show" button) to another parent, but always as the
308 first child position (insert at index 0).
309 """
310 if self._contacts.getParent():
311 if self._contacts.getParent() == parent:
312 return
313 self._contacts.removeFromParent()
314 parent.insert(self._contacts, 0)