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)