View Javadoc
1   /**
2    * This file Copyright (c) 2010-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
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   * Client side impl of lazy asset thumbnails layout.
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             // Scale the thumbnail divs.
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             // Scale the size of the image in the thumbnails.
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         // Either we we're passed null or there was no thumbnail with this id, so we make nothing selected
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 }