Mercurial > libervia-web
comparison browser/sat_browser/base_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/base_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 from sat.core.log import getLogger | |
21 log = getLogger(__name__) | |
22 from sat.core.i18n import _ | |
23 | |
24 from pyjamas.ui.VerticalPanel import VerticalPanel | |
25 from pyjamas.ui.HorizontalPanel import HorizontalPanel | |
26 from pyjamas.ui.ScrollPanel import ScrollPanel | |
27 from pyjamas.ui.Button import Button | |
28 from pyjamas.ui.SimplePanel import SimplePanel | |
29 from pyjamas.ui.PopupPanel import PopupPanel | |
30 from pyjamas.ui.StackPanel import StackPanel | |
31 from pyjamas.ui.TextArea import TextArea | |
32 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT | |
33 from pyjamas import DOM | |
34 | |
35 | |
36 ### Menus ### | |
37 | |
38 | |
39 class PopupMenuPanel(PopupPanel): | |
40 """Popup menu (contextual menu) with common callbacks for all the items. | |
41 | |
42 This implementation of a popup menu allow you to assign two special methods which | |
43 are common to all the items, in order to hide certain items and define their callbacks. | |
44 callbacks. The menu can be bound to any button of the mouse (left, middle, right). | |
45 """ | |
46 | |
47 def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs): | |
48 """ | |
49 @param entries (dict{unicode: dict{unicode: unicode}: | |
50 - menu item keys | |
51 - values: dict{unicode: unicode}: | |
52 - item data lile "title", "desc"... | |
53 - value | |
54 @param hide (callable): function of signature Widget, unicode: bool | |
55 which takes the sender and the item key, and returns True if that | |
56 item has to be hidden from the context menu. | |
57 @param callback (callbable): function of signature Widget, unicode: None | |
58 which takes the sender and the item key. | |
59 @param vertical (bool): set the direction vertical or horizontal | |
60 @param item_style (unicode): alternative CSS class for the menu items | |
61 @param menu_style (unicode): supplementary CSS class for the sender widget | |
62 """ | |
63 PopupPanel.__init__(self, autoHide=True, **kwargs) | |
64 self.entries = entries | |
65 self.hideMenu = hide | |
66 self.callback = callback | |
67 self.vertical = vertical | |
68 self.style = {"selected": None, "menu": "itemKeyMenu", "item": "popupMenuItem"} | |
69 if isinstance(style, dict): | |
70 self.style.update(style) | |
71 self.senders = {} | |
72 | |
73 def showMenu(self, sender): | |
74 """Popup the menu on the screen, where it fits to the sender's position. | |
75 | |
76 @param sender (Widget): the widget that has been clicked | |
77 """ | |
78 menu = VerticalPanel() if self.vertical is True else HorizontalPanel() | |
79 menu.setStyleName(self.style["menu"]) | |
80 | |
81 def button_cb(item): | |
82 # XXX: you can not put that method in the loop and rely on key | |
83 if self.callback is not None: | |
84 self.callback(sender=sender, key=item.key) | |
85 self.hide(autoClosed=True) | |
86 | |
87 for key, entry in self.entries.iteritems(): | |
88 if self.hideMenu is not None and self.hideMenu(sender=sender, key=key) is True: | |
89 continue | |
90 title = entry.get("title", key) | |
91 item = Button(title, button_cb, StyleName=self.style["item"]) | |
92 item.key = key # XXX: copy the key because we loop on it and it will change | |
93 item.setTitle(entry.get("desc", title)) | |
94 menu.add(item) | |
95 | |
96 if menu.getWidgetCount() == 0: | |
97 return # no item to display means no menu at all | |
98 | |
99 self.add(menu) | |
100 | |
101 if self.vertical is True: | |
102 x = sender.getAbsoluteLeft() + sender.getOffsetWidth() | |
103 y = sender.getAbsoluteTop() | |
104 else: | |
105 x = sender.getAbsoluteLeft() | |
106 y = sender.getAbsoluteTop() + sender.getOffsetHeight() | |
107 | |
108 self.setPopupPosition(x, y) | |
109 self.show() | |
110 | |
111 if self.style["selected"]: | |
112 sender.addStyleDependentName(self.style["selected"]) | |
113 | |
114 def onHide(popup): | |
115 if self.style["selected"]: | |
116 sender.removeStyleDependentName(self.style["selected"]) | |
117 return PopupPanel.onHideImpl(self, popup) | |
118 | |
119 self.onHideImpl = onHide | |
120 | |
121 def registerClickSender(self, sender, button=BUTTON_LEFT): | |
122 """Bind the menu to the specified sender. | |
123 | |
124 @param sender (Widget): bind the menu to this widget | |
125 @param (int): BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT | |
126 """ | |
127 self.senders.setdefault(sender, []) | |
128 self.senders[sender].append(button) | |
129 | |
130 if button == BUTTON_RIGHT: | |
131 # WARNING: to disable the context menu is a bit tricky... | |
132 # The following seems to work on Firefox 24.0, but: | |
133 # TODO: find a cleaner way to disable the context menu | |
134 sender.getElement().setAttribute("oncontextmenu", "return false") | |
135 | |
136 def onBrowserEvent(event): | |
137 button = DOM.eventGetButton(event) | |
138 if DOM.eventGetType(event) == "mousedown" and button in self.senders[sender]: | |
139 self.showMenu(sender) | |
140 return sender.__class__.onBrowserEvent(sender, event) | |
141 | |
142 sender.onBrowserEvent = onBrowserEvent | |
143 | |
144 def registerMiddleClickSender(self, sender): | |
145 self.registerClickSender(sender, BUTTON_MIDDLE) | |
146 | |
147 def registerRightClickSender(self, sender): | |
148 self.registerClickSender(sender, BUTTON_RIGHT) | |
149 | |
150 | |
151 ### Generic panels ### | |
152 | |
153 | |
154 class ToggleStackPanel(StackPanel): | |
155 """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be | |
156 visible at the same time, clicking a sub-panel header will not display it and hide | |
157 the others but only toggle its own visibility. The argument 'visibleStack' is ignored. | |
158 Note that the argument 'visible' has been added to listener's 'onStackChanged' method. | |
159 """ | |
160 | |
161 def __init__(self, **kwargs): | |
162 StackPanel.__init__(self, **kwargs) | |
163 | |
164 def onBrowserEvent(self, event): | |
165 if DOM.eventGetType(event) == "click": | |
166 index = self.getDividerIndex(DOM.eventGetTarget(event)) | |
167 if index != -1: | |
168 self.toggleStack(index) | |
169 | |
170 def add(self, widget, stackText="", asHTML=False, visible=False): | |
171 StackPanel.add(self, widget, stackText, asHTML) | |
172 self.setStackVisible(self.getWidgetCount() - 1, visible) | |
173 | |
174 def toggleStack(self, index): | |
175 if index >= self.getWidgetCount(): | |
176 return | |
177 visible = not self.getWidget(index).getVisible() | |
178 self.setStackVisible(index, visible) | |
179 for listener in self.stackListeners: | |
180 listener.onStackChanged(self, index, visible) | |
181 | |
182 | |
183 class TitlePanel(ToggleStackPanel): | |
184 """A toggle panel to set the message title""" | |
185 | |
186 TITLE = _("Title") | |
187 | |
188 def __init__(self, text=None): | |
189 ToggleStackPanel.__init__(self, Width="100%") | |
190 self.text_area = TextArea() | |
191 self.add(self.text_area, self.TITLE) | |
192 self.addStackChangeListener(self) | |
193 if text: | |
194 self.setText(text) | |
195 | |
196 def onStackChanged(self, sender, index, visible=None): | |
197 if visible is None: | |
198 visible = sender.getWidget(index).getVisible() | |
199 text = self.getText() | |
200 suffix = "" if (visible or not text) else (": %s" % text) | |
201 sender.setStackText(index, self.TITLE + suffix) | |
202 | |
203 def getText(self): | |
204 return self.text_area.getText() | |
205 | |
206 def setText(self, text): | |
207 self.text_area.setText(text) | |
208 | |
209 | |
210 class ScrollPanelWrapper(SimplePanel): | |
211 """Scroll Panel like component, wich use the full available space | |
212 to work around percent size issue, it use some of the ideas found | |
213 here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316 | |
214 specially in code given at comment #46, thanks to Stefan Bachert""" | |
215 | |
216 def __init__(self, *args, **kwargs): | |
217 SimplePanel.__init__(self) | |
218 self.spanel = ScrollPanel(*args, **kwargs) | |
219 SimplePanel.setWidget(self, self.spanel) | |
220 DOM.setStyleAttribute(self.getElement(), "position", "relative") | |
221 DOM.setStyleAttribute(self.getElement(), "top", "0px") | |
222 DOM.setStyleAttribute(self.getElement(), "left", "0px") | |
223 DOM.setStyleAttribute(self.getElement(), "width", "100%") | |
224 DOM.setStyleAttribute(self.getElement(), "height", "100%") | |
225 DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute") | |
226 DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%") | |
227 DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%") | |
228 | |
229 def setWidget(self, widget): | |
230 self.spanel.setWidget(widget) | |
231 | |
232 def setScrollPosition(self, position): | |
233 self.spanel.setScrollPosition(position) | |
234 | |
235 def scrollToBottom(self): | |
236 self.setScrollPosition(self.spanel.getElement().scrollHeight) |