1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
|
/*
@VaadinApache2LicenseForJavaFiles@
*/
package com.vaadin.terminal.gwt.client.ui;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.vaadin.terminal.gwt.client.BrowserInfo;
/**
* In Vaadin UI this Overlay should always be used for all elements that
* temporary float over other components like context menus etc. This is to deal
* stacking order correctly with VWindow objects.
*/
public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
/*
* The z-index value from where all overlays live. This can be overridden in
* any extending class.
*/
public static int Z_INDEX = 20000;
private static int leftFix = -1;
private static int topFix = -1;
/*
* Shadow element style. If an extending class wishes to use a different
* style of shadow, it can use setShadowStyle(String) to give the shadow
* element a new style name.
*/
public static final String CLASSNAME_SHADOW = "v-shadow";
/*
* The shadow element for this overlay.
*/
private Element shadow;
/**
* The HTML snippet that is used to render the actual shadow. In consists of
* nine different DIV-elements with the following class names:
*
* <pre>
* .v-shadow[-stylename]
* ----------------------------------------------
* | .top-left | .top | .top-right |
* |---------------|-----------|----------------|
* | | | |
* | .left | .center | .right |
* | | | |
* |---------------|-----------|----------------|
* | .bottom-left | .bottom | .bottom-right |
* ----------------------------------------------
* </pre>
*
* See default theme 'shadow.css' for implementation example.
*/
private static final String SHADOW_HTML = "<div class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>";
private boolean sinkShadowEvents = false;
public VOverlay() {
super();
adjustZIndex();
}
public VOverlay(boolean autoHide) {
super(autoHide);
adjustZIndex();
}
public VOverlay(boolean autoHide, boolean modal) {
super(autoHide, modal);
adjustZIndex();
}
public VOverlay(boolean autoHide, boolean modal, boolean showShadow) {
super(autoHide, modal);
setShadowEnabled(showShadow);
adjustZIndex();
}
/**
* Method to controle whether DOM elements for shadow are added. With this
* method subclasses can control displaying of shadow also after the
* constructor.
*
* @param enabled
* true if shadow should be displayed
*/
protected void setShadowEnabled(boolean enabled) {
if (enabled != isShadowEnabled()) {
if (enabled) {
shadow = DOM.createDiv();
shadow.setClassName(CLASSNAME_SHADOW);
shadow.setInnerHTML(SHADOW_HTML);
DOM.setStyleAttribute(shadow, "position", "absolute");
addCloseHandler(this);
} else {
removeShadowIfPresent();
shadow = null;
}
}
}
protected boolean isShadowEnabled() {
return shadow != null;
}
private void removeShadowIfPresent() {
if (isShadowAttached()) {
shadow.getParentElement().removeChild(shadow);
// Remove event listener from the shadow
unsinkShadowEvents();
}
}
private boolean isShadowAttached() {
return isShadowEnabled() && shadow.getParentElement() != null;
}
private void adjustZIndex() {
setZIndex(Z_INDEX);
}
/**
* Set the z-index (visual stack position) for this overlay.
*
* @param zIndex
* The new z-index
*/
protected void setZIndex(int zIndex) {
DOM.setStyleAttribute(getElement(), "zIndex", "" + zIndex);
if (isShadowEnabled()) {
DOM.setStyleAttribute(shadow, "zIndex", "" + zIndex);
}
}
@Override
public void setPopupPosition(int left, int top) {
// TODO, this should in fact be part of
// Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM
// for all permutations. Now adding fix as margin instead of fixing
// left/top because parent class saves the position.
Style style = getElement().getStyle();
style.setMarginLeft(-adjustByRelativeLeftBodyMargin(), Unit.PX);
style.setMarginTop(-adjustByRelativeTopBodyMargin(), Unit.PX);
super.setPopupPosition(left, top);
updateShadowSizeAndPosition(isAnimationEnabled() ? 0 : 1);
}
private static int adjustByRelativeTopBodyMargin() {
if (topFix == -1) {
topFix = detectRelativeBodyFixes("top");
}
return topFix;
}
private native static int detectRelativeBodyFixes(String axis)
/*-{
try {
var b = $wnd.document.body;
var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b);
if(cstyle && cstyle.position == 'relative') {
return b.getBoundingClientRect()[axis];
}
} catch(e){}
return 0;
}-*/;
private static int adjustByRelativeLeftBodyMargin() {
if (leftFix == -1) {
leftFix = detectRelativeBodyFixes("left");
}
return leftFix;
}
@Override
public void show() {
super.show();
if (isShadowEnabled()) {
if (isAnimationEnabled()) {
ShadowAnimation sa = new ShadowAnimation();
sa.run(200);
} else {
updateShadowSizeAndPosition(1.0);
}
}
}
@Override
protected void onDetach() {
super.onDetach();
// Always ensure shadow is removed when the overlay is removed.
removeShadowIfPresent();
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (isShadowEnabled()) {
shadow.getStyle().setProperty("visibility",
visible ? "visible" : "hidden");
}
}
@Override
public void setWidth(String width) {
super.setWidth(width);
updateShadowSizeAndPosition(1.0);
}
@Override
public void setHeight(String height) {
super.setHeight(height);
updateShadowSizeAndPosition(1.0);
}
/**
* Sets the shadow style for this overlay. Will override any previous style
* for the shadow. The default style name is defined by CLASSNAME_SHADOW.
* The given style will be prefixed with CLASSNAME_SHADOW.
*
* @param style
* The new style name for the shadow element. Will be prefixed by
* CLASSNAME_SHADOW, e.g. style=='foobar' -> actual style
* name=='v-shadow-foobar'.
*/
protected void setShadowStyle(String style) {
if (isShadowEnabled()) {
shadow.setClassName(CLASSNAME_SHADOW + "-" + style);
}
}
/*
* Extending classes should always call this method after they change the
* size of overlay without using normal 'setWidth(String)' and
* 'setHeight(String)' methods (if not calling super.setWidth/Height).
*/
public void updateShadowSizeAndPosition() {
updateShadowSizeAndPosition(1.0);
}
/**
* Recalculates proper position and dimensions for the shadow element. Can
* be used to animate the shadow, using the 'progress' parameter (used to
* animate the shadow in sync with GWT PopupPanel's default animation
* 'PopupPanel.AnimationType.CENTER').
*
* @param progress
* A value between 0.0 and 1.0, indicating the progress of the
* animation (0=start, 1=end).
*/
private void updateShadowSizeAndPosition(final double progress) {
// Don't do anything if overlay element is not attached
if (!isAttached() || shadow == null) {
return;
}
// Calculate proper z-index
String zIndex = null;
try {
// Odd behaviour with Windows Hosted Mode forces us to use
// this redundant try/catch block (See dev.vaadin.com #2011)
zIndex = DOM.getStyleAttribute(getElement(), "zIndex");
} catch (Exception ignore) {
// Ignored, will cause no harm
zIndex = "1000";
}
if (zIndex == null) {
zIndex = "" + Z_INDEX;
}
// Calculate position and size
if (BrowserInfo.get().isIE()) {
// Shake IE
getOffsetHeight();
getOffsetWidth();
}
int x = getAbsoluteLeft();
int y = getAbsoluteTop();
/* This is needed for IE7 at least */
// Account for the difference between absolute position and the
// body's positioning context.
x -= Document.get().getBodyOffsetLeft();
y -= Document.get().getBodyOffsetTop();
x -= adjustByRelativeLeftBodyMargin();
y -= adjustByRelativeTopBodyMargin();
int width = getOffsetWidth();
int height = getOffsetHeight();
if (width < 0) {
width = 0;
}
if (height < 0) {
height = 0;
}
// Animate the shadow size
x += (int) (width * (1.0 - progress) / 2.0);
y += (int) (height * (1.0 - progress) / 2.0);
width = (int) (width * progress);
height = (int) (height * progress);
// Opera needs some shaking to get parts of the shadow showing
// properly
// (ticket #2704)
if (BrowserInfo.get().isOpera()) {
// Clear the height of all middle elements
DOM.getChild(shadow, 3).getStyle().setProperty("height", "auto");
DOM.getChild(shadow, 4).getStyle().setProperty("height", "auto");
DOM.getChild(shadow, 5).getStyle().setProperty("height", "auto");
}
// Update correct values
DOM.setStyleAttribute(shadow, "zIndex", zIndex);
DOM.setStyleAttribute(shadow, "width", width + "px");
DOM.setStyleAttribute(shadow, "height", height + "px");
DOM.setStyleAttribute(shadow, "top", y + "px");
DOM.setStyleAttribute(shadow, "left", x + "px");
DOM.setStyleAttribute(shadow, "display", progress < 0.9 ? "none" : "");
// Opera fix, part 2 (ticket #2704)
if (BrowserInfo.get().isOpera()) {
// We'll fix the height of all the middle elements
DOM.getChild(shadow, 3)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 3).getOffsetHeight());
DOM.getChild(shadow, 4)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 4).getOffsetHeight());
DOM.getChild(shadow, 5)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 5).getOffsetHeight());
}
// Attach to dom if not there already
if (!isShadowAttached()) {
RootPanel.get().getElement().insertBefore(shadow, getElement());
sinkShadowEvents();
}
}
protected class ShadowAnimation extends Animation {
@Override
protected void onUpdate(double progress) {
updateShadowSizeAndPosition(progress);
}
}
public void onClose(CloseEvent<PopupPanel> event) {
removeShadowIfPresent();
}
@Override
public void sinkEvents(int eventBitsToAdd) {
super.sinkEvents(eventBitsToAdd);
// Also sink events on the shadow if present
sinkShadowEvents();
}
private void sinkShadowEvents() {
if (isSinkShadowEvents() && isShadowAttached()) {
// Sink the same events as the actual overlay has sunk
DOM.sinkEvents(shadow, DOM.getEventsSunk(getElement()));
// Send events to VOverlay.onBrowserEvent
DOM.setEventListener(shadow, this);
}
}
private void unsinkShadowEvents() {
if (isShadowAttached()) {
DOM.setEventListener(shadow, null);
DOM.sinkEvents(shadow, 0);
}
}
/**
* Enables or disables sinking the events of the shadow to the same
* onBrowserEvent as events to the actual overlay goes.
*
* Please note, that if you enable this, you can't assume that e.g.
* event.getEventTarget returns an element inside the DOM structure of the
* overlay
*
* @param sinkShadowEvents
*/
protected void setSinkShadowEvents(boolean sinkShadowEvents) {
this.sinkShadowEvents = sinkShadowEvents;
if (sinkShadowEvents) {
sinkShadowEvents();
} else {
unsinkShadowEvents();
}
}
protected boolean isSinkShadowEvents() {
return sinkShadowEvents;
}
}
|