1 /*
2  * Copyright (C) 2008 Emweb bv, Herent, Belgium.
3  *
4  * See the LICENSE file for terms of use.
5  */
6 #include "Wt/WApplication.h"
7 #include "Wt/WEnvironment.h"
8 #include "Wt/WException.h"
9 #include "Wt/WContainerWidget.h"
10 #include "Wt/WCssDecorationStyle.h"
11 #include "Wt/WImage.h"
12 #include "Wt/WResource.h"
13 #include "Wt/WVirtualImage.h"
14 #include "WebUtils.h"
15 
16 #include <algorithm>
17 
18 namespace {
19 
clamp(::int64_t v,::int64_t min,::int64_t max)20 inline ::int64_t clamp(::int64_t v, ::int64_t min, ::int64_t max)
21 {
22   return std::max(min, std::min(v, max));
23 }
24 
25 }
26 
27 namespace Wt {
28 
29 const ::int64_t WVirtualImage::Infinite
30   = std::numeric_limits< ::int64_t >::max();
31 
WVirtualImage(int viewPortWidth,int viewPortHeight,::int64_t imageWidth,::int64_t imageHeight,int gridImageSize)32 WVirtualImage::WVirtualImage(int viewPortWidth, int viewPortHeight,
33 			     ::int64_t imageWidth, ::int64_t imageHeight,
34 			     int gridImageSize)
35   : gridImageSize_(gridImageSize),
36     viewPortWidth_(viewPortWidth),
37     viewPortHeight_(viewPortHeight),
38     imageWidth_(imageWidth),
39     imageHeight_(imageHeight),
40     currentX_(0),
41     currentY_(0)
42 {
43   setImplementation(std::unique_ptr<WContainerWidget>(impl_ = new WContainerWidget()));
44 
45   impl_->resize(viewPortWidth_, viewPortHeight_);
46   impl_->setPositionScheme(PositionScheme::Relative);
47 
48   WContainerWidget *scrollArea
49     = impl_->addWidget(std::make_unique<WContainerWidget>());
50   scrollArea->resize(WLength(100, LengthUnit::Percentage),
51 		     WLength(100, LengthUnit::Percentage));
52   scrollArea->setPositionScheme(PositionScheme::Absolute);
53   scrollArea->setOverflow(Overflow::Hidden);
54 
55   contents_ = scrollArea->addWidget(std::make_unique<WContainerWidget>());
56   contents_->setPositionScheme(PositionScheme::Absolute);
57 }
58 
enableDragging()59 void WVirtualImage::enableDragging()
60 {
61   /*
62    * For dragging the virtual image, in client-side JavaScript if available.
63    */
64   impl_->mouseWentDown().connect("function(obj, event) {"
65      "  var pc = " WT_CLASS ".pageCoordinates(event);"
66      "  obj.setAttribute('dsx', pc.x);"
67      "  obj.setAttribute('dsy', pc.y);"
68      "}");
69 
70   impl_->mouseMoved().connect("function(obj, event) {"
71      """var WT= " WT_CLASS ";"
72      """var lastx = obj.getAttribute('dsx');"
73      """var lasty = obj.getAttribute('dsy');"
74      """if (lastx != null && lastx != '') {"
75      ""  "var nowxy = WT.pageCoordinates(event);"
76      ""  "var img = " + contents_->jsRef() + ";"
77      ""  "img.style.left = (WT.pxself(img, 'left')+nowxy.x-lastx) + 'px';"
78      ""  "img.style.top = (WT.pxself(img, 'top')+nowxy.y-lasty) + 'px';"
79      ""  "obj.setAttribute('dsx', nowxy.x);"
80      ""  "obj.setAttribute('dsy', nowxy.y);"
81      """}"
82      "}");
83 
84   impl_->mouseWentUp().connect("function(obj, event) {"
85      + impl_->jsRef() + ".removeAttribute('dsx');"
86      "}");
87 
88   impl_->mouseWentUp().connect(this, &WVirtualImage::mouseUp);
89   impl_->decorationStyle().setCursor(Cursor::OpenHand);
90 }
91 
~WVirtualImage()92 WVirtualImage::~WVirtualImage()
93 { }
94 
mouseUp(const WMouseEvent & e)95 void WVirtualImage::mouseUp(const WMouseEvent& e)
96 {
97   internalScrollTo(currentX_ - e.dragDelta().x, currentY_ - e.dragDelta().y,
98 		   !WApplication::instance()->environment().ajax());
99 }
100 
redrawAll()101 void WVirtualImage::redrawAll()
102 {
103   contents_->clear();
104   grid_.clear();
105 
106   generateGridItems(currentX_, currentY_);
107 }
108 
resizeImage(::int64_t w,::int64_t h)109 void WVirtualImage::resizeImage(::int64_t w, ::int64_t h)
110 {
111   imageWidth_ = w;
112   imageHeight_ = h;
113 
114   redrawAll();
115 }
116 
scrollTo(::int64_t newX,::int64_t newY)117 void WVirtualImage::scrollTo(::int64_t newX, ::int64_t newY)
118 {
119   internalScrollTo(newX, newY, true);
120 }
121 
internalScrollTo(::int64_t newX,::int64_t newY,bool moveViewPort)122 void WVirtualImage::internalScrollTo(::int64_t newX, ::int64_t newY,
123 				     bool moveViewPort)
124 {
125   if (imageWidth_ != Infinite)
126     newX = clamp(newX, 0, imageWidth_ - viewPortWidth_);
127   if (imageHeight_ != Infinite)
128     newY = clamp(newY, 0, imageHeight_ - viewPortHeight_);
129 
130   if (moveViewPort) {
131     contents_->setOffsets((double)-newX, Side::Left);
132     contents_->setOffsets((double)-newY, Side::Top);
133   }
134 
135   generateGridItems(newX, newY);
136 
137   viewPortChanged_.emit(currentX_, currentY_);
138 }
139 
scroll(::int64_t dx,::int64_t dy)140 void WVirtualImage::scroll(::int64_t dx, ::int64_t dy)
141 {
142   scrollTo(currentX_ + dx, currentY_ + dy);
143 }
144 
145 std::unique_ptr<WImage> WVirtualImage
createImage(::int64_t x,::int64_t y,int width,int height)146 ::createImage(::int64_t x, ::int64_t y, int width, int height)
147 {
148   auto r = render(x, y, width, height);
149   return std::unique_ptr<WImage>
150     (new WImage(std::shared_ptr<WResource>(std::move(r)), ""));
151 }
152 
153 std::unique_ptr<WResource> WVirtualImage
render(::int64_t x,::int64_t y,int width,int height)154 ::render(::int64_t x, ::int64_t y, int width, int height)
155 {
156   throw WException("You should reimplement WVirtualImage::render()");
157 }
158 
generateGridItems(::int64_t newX,::int64_t newY)159 void WVirtualImage::generateGridItems(::int64_t newX, ::int64_t newY)
160 {
161   /*
162    * The coordinates of the two extreme corners of the new rendered
163    * neighbourhood
164    */
165   Rect newNb = neighbourhood(newX, newY, viewPortWidth_, viewPortHeight_);
166 
167   ::int64_t i1 = newNb.x1 / gridImageSize_;
168   ::int64_t j1 = newNb.y1 / gridImageSize_;
169   ::int64_t i2 = newNb.x2 / gridImageSize_ + 1;
170   ::int64_t j2 = newNb.y2 / gridImageSize_ + 1;
171 
172   for (int invisible = 0; invisible < 2; ++invisible) {
173     for (::int64_t i = i1; i < i2; ++i)
174       for (::int64_t j = j1; j < j2; ++j) {
175 	::int64_t key = gridKey(i, j);
176 
177 	GridMap::iterator it = grid_.find(key);
178 	if (it == grid_.end()) {
179 	  bool v = visible(i, j);
180 	  if ((v && !invisible) || (!v && invisible)) {
181 	    ::int64_t brx = i * gridImageSize_ + gridImageSize_;
182 	    ::int64_t bry = j * gridImageSize_ + gridImageSize_;
183 	    brx = std::min(brx, imageWidth_);
184 	    bry = std::min(bry, imageHeight_);
185 
186             const int width = static_cast<int>(brx - i * gridImageSize_);
187             const int height = static_cast<int>(bry - j * gridImageSize_);
188             if (width > 0 && height > 0) {
189 	      std::unique_ptr<WImage> img
190 	        = createImage(i * gridImageSize_, j * gridImageSize_, width, height);
191 
192               img->mouseWentDown().preventDefaultAction(true);
193 	      img->setPositionScheme(PositionScheme::Absolute);
194 	      img->setOffsets((double)i * gridImageSize_, Side::Left);
195 	      img->setOffsets((double)j * gridImageSize_, Side::Top);
196 
197 	      grid_[key] = img.get();
198 
199 	      contents_->addWidget(std::move(img));
200             }
201 	  }
202 	}
203       }
204   }
205 
206   currentX_ = newX;
207   currentY_ = newY;
208 
209   cleanGrid();
210 }
211 
gridKey(::int64_t i,::int64_t j)212 ::int64_t WVirtualImage::gridKey(::int64_t i, ::int64_t j)
213 {
214   return i * 1000 + j; // I should consider fixing this properly ...
215 }
216 
visible(::int64_t i,::int64_t j)217 bool WVirtualImage::visible(::int64_t i, ::int64_t j) const
218 {
219   ::int64_t x1 = i * gridImageSize_;
220   ::int64_t y1 = j * gridImageSize_;
221   ::int64_t x2 = x1 + gridImageSize_;
222   ::int64_t y2 = y1 + gridImageSize_;
223 
224   return ((x2 >= currentX_) && (y2 >= currentY_)
225 	  && (x1 <= currentX_ + viewPortWidth_)
226 	  && (y1 <= currentY_ + viewPortHeight_));
227 }
228 
decodeKey(::int64_t key,Coordinate & coordinate)229 void WVirtualImage::decodeKey(::int64_t key, Coordinate& coordinate)
230 {
231   coordinate.i = key / 1000;
232   coordinate.j = key % 1000;
233 }
234 
cleanGrid()235 void WVirtualImage::cleanGrid()
236 {
237   Rect cleanNb = neighbourhood(currentX_, currentY_,
238 			       viewPortWidth_ * 3, viewPortHeight_ * 3);
239 
240   ::int64_t i1 = cleanNb.x1 / gridImageSize_;
241   ::int64_t j1 = cleanNb.y1 / gridImageSize_;
242   ::int64_t i2 = cleanNb.x2 / gridImageSize_ + 1;
243   ::int64_t j2 = cleanNb.y2 / gridImageSize_ + 1;
244 
245   for (GridMap::iterator it = grid_.begin(); it != grid_.end();) {
246     Coordinate coordinate;
247     decodeKey(it->first, coordinate);
248 
249     if (coordinate.i < i1 || coordinate.i > i2 ||
250 	coordinate.j < j1 || coordinate.j > j2) {
251       it->second->removeFromParent();
252       Utils::eraseAndNext(grid_, it);
253     } else
254       ++it;
255   }
256 }
257 
neighbourhood(::int64_t x,::int64_t y,int marginX,int marginY)258 WVirtualImage::Rect WVirtualImage::neighbourhood(::int64_t x, ::int64_t y,
259 						 int marginX, int marginY)
260 {
261   ::int64_t x1 = x - marginX;
262 
263   if (imageWidth_ != Infinite)
264     x1 = std::max((::int64_t)0, x1);
265 
266   ::int64_t y1 = std::max((::int64_t)0, y - marginY);
267 
268   ::int64_t x2 = x + viewPortWidth_ + marginX;
269   if (imageWidth_ != Infinite)
270     x2 = std::min(imageWidth_, x2);
271 
272   ::int64_t y2 = std::min(imageHeight_, y + viewPortHeight_ + marginY);
273 
274   return Rect(x1, y1, x2, y2);
275 }
276 
277 }
278