Mercurial > libervia-web
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 = '<click to set a status>' | |
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) |