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 package info.magnolia.ui.vaadin.gwt.client.layout.thumbnaillayout.widget;
35
36 import info.magnolia.ui.vaadin.gwt.client.layout.CssRule;
37 import info.magnolia.ui.vaadin.gwt.client.layout.thumbnaillayout.connector.LazyThumbnailLayoutConnector.ThumbnailService;
38 import info.magnolia.ui.vaadin.gwt.client.layout.thumbnaillayout.shared.ThumbnailData;
39 import info.magnolia.ui.vaadin.gwt.client.pinch.MagnoliaPinchMoveEvent;
40 import info.magnolia.ui.vaadin.gwt.client.pinch.MagnoliaPinchRecognizer;
41
42 import java.util.ArrayList;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.logging.Level;
46 import java.util.logging.Logger;
47
48 import com.google.gwt.dom.client.Style.Position;
49 import com.google.gwt.dom.client.Style.Unit;
50 import com.google.gwt.event.dom.client.ScrollEvent;
51 import com.google.gwt.event.dom.client.ScrollHandler;
52 import com.google.gwt.event.logical.shared.ValueChangeEvent;
53 import com.google.gwt.event.logical.shared.ValueChangeHandler;
54 import com.google.gwt.user.client.DOM;
55 import com.google.gwt.user.client.Event;
56 import com.google.gwt.user.client.Timer;
57 import com.google.gwt.user.client.ui.FlowPanel;
58 import com.google.gwt.user.client.ui.ScrollPanel;
59 import com.googlecode.mgwt.dom.client.recognizer.pinch.UIObjectToOffsetProvider;
60 import com.googlecode.mgwt.dom.client.recognizer.tap.MultiTapRecognizer;
61 import com.googlecode.mgwt.ui.client.widget.touch.TouchDelegate;
62 import com.vaadin.client.ComputedStyle;
63 import com.vaadin.client.ServerConnector;
64
65
66
67
68 public class LazyThumbnailLayoutWidget extends FlowPanel {
69
70 private static final int QUERY_TIMER_DELAY = 250;
71
72 private static final int MIN_WIDTH = 50;
73
74 private static final int MAX_WIDTH = 230;
75
76 private int thumbnailWidth = 0;
77
78 private int thumbnailHeight = 0;
79
80 private int thumbnailAmount = 0;
81
82 private ThumbnailWidget selectedThumbnail = null;
83
84 private final List<ThumbnailWidget> thumbnails = new ArrayList<ThumbnailWidget>();
85
86 private final List<ThumbnailWidget> thumbnailStubs = new ArrayList<ThumbnailWidget>();
87
88 private final ScrollPanel scroller = new ScrollPanel();
89
90 private final Slider thumbnailSizeSlider = new Slider();
91
92 private final CssRule thumbnailImageStyle = CssRule.create(".thumbnail-image");
93
94 private final CssRule thumbnailStyle = CssRule.create(".thumbnail");
95
96 private final FlowPanel imageContainer = new FlowPanel();
97
98 private ThumbnailService thumbnailService;
99
100 public LazyThumbnailLayoutWidget() {
101
102 addStyleName("thumbnail-layout");
103
104 thumbnailStyle.setProperty("margin", "3px");
105
106 scroller.setWidget(imageContainer);
107 scroller.addStyleName("thumbnail-scroller");
108 scroller.getElement().getStyle().setPosition(Position.ABSOLUTE);
109
110 add(thumbnailSizeSlider);
111 add(scroller);
112
113 bindHandlers();
114 reset();
115 }
116
117 protected void bindHandlers() {
118
119 scroller.addScrollHandler(new ScrollHandler() {
120 @Override
121 public void onScroll(ScrollEvent event) {
122 createStubsAndQueryThumbnails();
123 }
124 });
125
126 final TouchDelegate touchDelegate = new TouchDelegate(this);
127 touchDelegate.addTouchHandler(new MagnoliaPinchRecognizer(touchDelegate, new UIObjectToOffsetProvider(scroller)));
128 MultiTapRecognizer multitapRecognizer = new MultiTapRecognizer(touchDelegate, 1, 2);
129 touchDelegate.addTouchHandler(multitapRecognizer);
130
131 addHandler(new MagnoliaPinchMoveEvent.Handler() {
132 @Override
133 public void onPinchMove(MagnoliaPinchMoveEvent event) {
134 double scaleFactor = 1 / event.getScaleFactor();
135 int width = Math.max((int) (ComputedStyle.parseInt(thumbnailStyle.getProperty("width")) * scaleFactor), 25);
136 int height = Math.max((int) (ComputedStyle.parseInt(thumbnailStyle.getProperty("height")) * scaleFactor), 25);
137 scroller.setVerticalScrollPosition((int) (scroller.getVerticalScrollPosition() * scaleFactor));
138 setThumbnailSize(width, height);
139
140 }
141 }, MagnoliaPinchMoveEvent.TYPE);
142
143 thumbnailSizeSlider.addValueChangeHandler(new ValueChangeHandler<Integer>() {
144 @Override
145 public void onValueChange(ValueChangeEvent<Integer> event) {
146
147 double scaleFactor = (double) event.getValue() / 100.0;
148 int widthRequest = (int) (MIN_WIDTH + (MAX_WIDTH - MIN_WIDTH) * scaleFactor);
149 int width = Math.max(widthRequest, 10);
150 setThumbnailSize(width, width);
151 }
152 });
153
154 DOM.sinkEvents(getElement(), Event.MOUSEEVENTS | Event.ONSCROLL);
155 }
156
157 private void createStubsAndQueryThumbnails() {
158 int thumbnailsNeeded = calculateThumbnailsNeeded();
159 addStubs(thumbnailsNeeded);
160 queryTimer.schedule(QUERY_TIMER_DELAY);
161 }
162
163 private void addStubs(int thumbnailsNeeded) {
164 for (int i = 0; i < thumbnailsNeeded; ++i) {
165 addStub();
166 }
167 }
168
169 private void addStub() {
170 final ThumbnailWidget thumbnailStub = new ThumbnailWidget();
171 thumbnailStubs.add(thumbnailStub);
172 imageContainer.add(thumbnailStub);
173 }
174
175 public void setThumbnailSize(int width, int height) {
176 Logger.getLogger(getClass().getName()).log(Level.INFO, "Thumbnails: setThumbnailSize: " + width + " h:" + height);
177 if (this.thumbnailHeight != height || this.thumbnailWidth != width) {
178 this.thumbnailHeight = height;
179 this.thumbnailWidth = width;
180
181
182 thumbnailStyle.setProperty("width", width + "px");
183 thumbnailStyle.setProperty("height", width + "px");
184 String fontSize = String.valueOf((int) (width * 0.75));
185 thumbnailStyle.setProperty("fontSize", fontSize + "px");
186
187
188 thumbnailImageStyle.setProperty("maxWidth", width + "px");
189 thumbnailImageStyle.setProperty("maxHeight", width + "px");
190
191 createStubsAndQueryThumbnails();
192 }
193 }
194
195 private final Timer queryTimer = new Timer() {
196 @Override
197 public void run() {
198 doQueryThumbnails(thumbnailStubs.size());
199 }
200
201 ;
202 };
203
204 public void addImages(List<ThumbnailData> thumbnailsData, ServerConnector connector) {
205 final Iterator<ThumbnailData> it = thumbnailsData.iterator();
206 while (it.hasNext() && !thumbnailStubs.isEmpty()) {
207 final ThumbnailWidget thumbnail = thumbnailStubs.remove(0);
208 thumbnail.setData(it.next(), connector);
209 thumbnails.add(thumbnail);
210 }
211 }
212
213 public void setThumbnailAmount(int thumbnailAmount) {
214 if (this.thumbnailAmount != thumbnailAmount) {
215 this.thumbnailAmount = thumbnailAmount;
216 int width = getOffsetWidth();
217 int totalThumbnailWidth = (thumbnailWidth + getHorizontalMargin());
218 if (totalThumbnailWidth != 0) {
219 int thumbnailsInRow = (int) (width / totalThumbnailWidth * 1d);
220 if (thumbnailsInRow != 0) {
221 int pixelHeight = (int) Math.ceil(thumbnailAmount / thumbnailsInRow * 1d) * (thumbnailHeight + getVerticalMargin());
222 imageContainer.getElement().getStyle().setHeight(pixelHeight, Unit.PX);
223 createStubsAndQueryThumbnails();
224 }
225 }
226 }
227 }
228
229 private void doQueryThumbnails(int amount) {
230 if (amount > 0) {
231 thumbnailService.loadThumbnails(amount);
232 }
233 }
234
235 public void reset() {
236 thumbnails.clear();
237 thumbnailStubs.clear();
238 imageContainer.clear();
239 selectedThumbnail = null;
240 thumbnailAmount = 0;
241 int thumbnailsNeeded = calculateThumbnailsNeeded();
242 addStubs(thumbnailsNeeded);
243 doQueryThumbnails(thumbnailsNeeded);
244 }
245
246 private int calculateThumbnailsNeeded() {
247 int totalHeight = scroller.getVerticalScrollPosition() + getOffsetHeight();
248 int width = getOffsetWidth();
249 int thumbnailsInRow = (int) (width / (thumbnailWidth + getHorizontalMargin()) * 1d);
250 int rows = (int) Math.ceil(1d * totalHeight / (thumbnailHeight + getVerticalMargin()));
251 int totalThumbnailsPossible = Math.min(thumbnailAmount, thumbnailsInRow * rows);
252 return Math.max(totalThumbnailsPossible - thumbnailStubs.size() - thumbnails.size(), 0);
253 }
254
255 public void generateStubs() {
256 addStubs(calculateThumbnailsNeeded());
257 queryTimer.schedule(QUERY_TIMER_DELAY);
258 }
259
260 private int getHorizontalMargin() {
261 return ComputedStyle.parseInt(thumbnailStyle.getProperty("marginTop")) * 2;
262 }
263
264 private int getVerticalMargin() {
265 return ComputedStyle.parseInt(thumbnailStyle.getProperty("marginLeft")) * 2;
266 }
267
268 public void setSelectedThumbnail(String thumbnailId) {
269 if (thumbnailId != null) {
270 for (ThumbnailWidget thumbnail : thumbnails) {
271 if (thumbnail.getId().equals(thumbnailId)) {
272 setSelectedThumbnail(thumbnail);
273 return;
274 }
275 }
276 }
277
278 setSelectedThumbnail((ThumbnailWidget)null);
279 }
280
281 public void setSelectedThumbnail(ThumbnailWidget thumbnail) {
282 if (selectedThumbnail != null) {
283 selectedThumbnail.setSelected(false);
284 }
285 if (thumbnail != null) {
286 thumbnail.setSelected(true);
287 }
288 selectedThumbnail = thumbnail;
289 }
290
291 @Override
292 public void clear() {
293 thumbnailStubs.addAll(thumbnails);
294 thumbnails.clear();
295 }
296
297 public void setThumbnailService(ThumbnailService thumbnailService) {
298 this.thumbnailService = thumbnailService;
299 }
300 }