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