1804
|
1 /**
|
|
2 * Stone of Orthanc
|
|
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
4 * Department, University Hospital of Liege, Belgium
|
|
5 * Copyright (C) 2017-2021 Osimis S.A., Belgium
|
|
6 *
|
|
7 * This program is free software: you can redistribute it and/or
|
|
8 * modify it under the terms of the GNU Lesser General Public License
|
|
9 * as published by the Free Software Foundation, either version 3 of
|
|
10 * the License, or (at your option) any later version.
|
|
11 *
|
|
12 * This program is distributed in the hope that it will be useful, but
|
|
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
15 * Lesser General Public License for more details.
|
|
16 *
|
|
17 * You should have received a copy of the GNU Lesser General Public
|
|
18 * License along with this program. If not, see
|
|
19 * <http://www.gnu.org/licenses/>.
|
|
20 **/
|
|
21
|
|
22
|
|
23 #include "AnnotationsSceneLayer.h"
|
|
24
|
|
25 #include "MacroSceneLayer.h"
|
|
26 #include "PolylineSceneLayer.h"
|
|
27 #include "TextSceneLayer.h"
|
|
28
|
|
29 #include <OrthancException.h>
|
|
30
|
|
31 #include <boost/math/constants/constants.hpp>
|
|
32 #include <list>
|
|
33
|
|
34 static const double HANDLE_SIZE = 10.0;
|
|
35 static const double PI = boost::math::constants::pi<double>();
|
|
36
|
|
37 static const char* const KEY_ANNOTATIONS = "annotations";
|
|
38 static const char* const KEY_TYPE = "type";
|
|
39 static const char* const KEY_X1 = "x1";
|
|
40 static const char* const KEY_Y1 = "y1";
|
|
41 static const char* const KEY_X2 = "x2";
|
|
42 static const char* const KEY_Y2 = "y2";
|
|
43 static const char* const KEY_X3 = "x3";
|
|
44 static const char* const KEY_Y3 = "y3";
|
|
45
|
|
46 static const char* const VALUE_ANGLE = "angle";
|
|
47 static const char* const VALUE_CIRCLE = "circle";
|
|
48 static const char* const VALUE_SEGMENT = "segment";
|
|
49
|
|
50
|
|
51 namespace OrthancStone
|
|
52 {
|
|
53 class AnnotationsSceneLayer::GeometricPrimitive : public boost::noncopyable
|
|
54 {
|
|
55 private:
|
|
56 bool modified_;
|
|
57 Annotation& parentAnnotation_;
|
|
58 Color color_;
|
|
59 Color hoverColor_;
|
|
60 bool isHover_;
|
|
61 int depth_;
|
|
62
|
|
63 public:
|
|
64 GeometricPrimitive(Annotation& parentAnnotation,
|
|
65 int depth) :
|
|
66 modified_(true),
|
|
67 parentAnnotation_(parentAnnotation),
|
|
68 color_(192, 192, 192),
|
|
69 hoverColor_(0, 255, 0),
|
|
70 isHover_(false),
|
|
71 depth_(depth)
|
|
72 {
|
|
73 }
|
|
74
|
|
75 virtual ~GeometricPrimitive()
|
|
76 {
|
|
77 }
|
|
78
|
|
79 Annotation& GetParentAnnotation() const
|
|
80 {
|
|
81 return parentAnnotation_;
|
|
82 }
|
|
83
|
|
84 int GetDepth() const
|
|
85 {
|
|
86 return depth_;
|
|
87 }
|
|
88
|
|
89 void SetHover(bool hover)
|
|
90 {
|
|
91 if (hover != isHover_)
|
|
92 {
|
|
93 isHover_ = hover;
|
|
94 modified_ = true;
|
|
95 }
|
|
96 }
|
|
97
|
|
98 bool IsHover() const
|
|
99 {
|
|
100 return isHover_;
|
|
101 }
|
|
102
|
|
103 void SetModified(bool modified)
|
|
104 {
|
|
105 modified_ = modified;
|
|
106 }
|
|
107
|
|
108 bool IsModified() const
|
|
109 {
|
|
110 return modified_;
|
|
111 }
|
|
112
|
|
113 void SetColor(const Color& color)
|
|
114 {
|
|
115 SetModified(true);
|
|
116 color_ = color;
|
|
117 }
|
|
118
|
|
119 void SetHoverColor(const Color& color)
|
|
120 {
|
|
121 SetModified(true);
|
|
122 hoverColor_ = color;
|
|
123 }
|
|
124
|
|
125 const Color& GetColor() const
|
|
126 {
|
|
127 return color_;
|
|
128 }
|
|
129
|
|
130 const Color& GetHoverColor() const
|
|
131 {
|
|
132 return hoverColor_;
|
|
133 }
|
|
134
|
|
135 virtual bool IsHit(const ScenePoint2D& p,
|
|
136 const Scene2D& scene) const = 0;
|
|
137
|
|
138 // Always called, even if not modified
|
|
139 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
140 const Scene2D& scene) = 0;
|
|
141
|
|
142 // Only called if modified
|
|
143 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
144 const Scene2D& scene) = 0;
|
|
145
|
|
146 virtual void MovePreview(const ScenePoint2D& delta) = 0;
|
|
147
|
|
148 virtual void MoveDone(const ScenePoint2D& delta) = 0;
|
|
149 };
|
|
150
|
|
151
|
|
152 class AnnotationsSceneLayer::Annotation : public boost::noncopyable
|
|
153 {
|
|
154 private:
|
|
155 typedef std::list<GeometricPrimitive*> GeometricPrimitives;
|
|
156
|
|
157 AnnotationsSceneLayer& that_;
|
|
158 GeometricPrimitives primitives_;
|
|
159
|
|
160 public:
|
|
161 Annotation(AnnotationsSceneLayer& that) :
|
|
162 that_(that)
|
|
163 {
|
|
164 that.AddAnnotation(this);
|
|
165 }
|
|
166
|
|
167 virtual ~Annotation()
|
|
168 {
|
|
169 for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
|
|
170 {
|
|
171 that_.DeletePrimitive(*it);
|
|
172 }
|
|
173 }
|
|
174
|
|
175 GeometricPrimitive* AddPrimitive(GeometricPrimitive* primitive)
|
|
176 {
|
|
177 if (primitive == NULL)
|
|
178 {
|
|
179 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
|
|
180 }
|
|
181 else
|
|
182 {
|
|
183 assert(that_.primitives_.find(primitive) == that_.primitives_.end());
|
|
184 primitives_.push_back(primitive); // For automated deallocation
|
|
185 that_.primitives_.insert(primitive);
|
|
186 return primitive;
|
|
187 }
|
|
188 }
|
|
189
|
|
190 template <typename T>
|
|
191 T& AddTypedPrimitive(T* primitive)
|
|
192 {
|
|
193 AddPrimitive(primitive);
|
|
194 return *primitive;
|
|
195 }
|
|
196
|
|
197 virtual void SignalMove(GeometricPrimitive& primitive) = 0;
|
|
198
|
|
199 virtual void Serialize(Json::Value& target) = 0;
|
|
200 };
|
|
201
|
|
202
|
|
203 class AnnotationsSceneLayer::Handle : public GeometricPrimitive
|
|
204 {
|
|
205 private:
|
|
206 ScenePoint2D center_;
|
|
207 ScenePoint2D delta_;
|
|
208
|
|
209 public:
|
|
210 explicit Handle(Annotation& parentAnnotation,
|
|
211 const ScenePoint2D& center) :
|
|
212 GeometricPrimitive(parentAnnotation, 0), // Highest priority
|
|
213 center_(center),
|
|
214 delta_(0, 0)
|
|
215 {
|
|
216 }
|
|
217
|
|
218 void SetSize(unsigned int size)
|
|
219 {
|
|
220 SetModified(true);
|
|
221 }
|
|
222
|
|
223 void SetCenter(const ScenePoint2D& center)
|
|
224 {
|
|
225 SetModified(true);
|
|
226 center_ = center;
|
|
227 delta_ = ScenePoint2D(0, 0);
|
|
228 }
|
|
229
|
|
230 ScenePoint2D GetCenter() const
|
|
231 {
|
|
232 return center_ + delta_;
|
|
233 }
|
|
234
|
|
235 virtual bool IsHit(const ScenePoint2D& p,
|
|
236 const Scene2D& scene) const
|
|
237 {
|
|
238 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
|
|
239
|
|
240 double dx = (center_.GetX() + delta_.GetX() - p.GetX()) * zoom;
|
|
241 double dy = (center_.GetY() + delta_.GetY() - p.GetY()) * zoom;
|
|
242
|
|
243 return (std::abs(dx) <= HANDLE_SIZE / 2.0 &&
|
|
244 std::abs(dy) <= HANDLE_SIZE / 2.0);
|
|
245 }
|
|
246
|
|
247 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
248 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
249 {
|
|
250 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
|
|
251
|
|
252 // TODO: take DPI into account
|
|
253 double x1 = center_.GetX() + delta_.GetX() - (HANDLE_SIZE / 2.0) / zoom;
|
|
254 double y1 = center_.GetY() + delta_.GetY() - (HANDLE_SIZE / 2.0) / zoom;
|
|
255 double x2 = center_.GetX() + delta_.GetX() + (HANDLE_SIZE / 2.0) / zoom;
|
|
256 double y2 = center_.GetY() + delta_.GetY() + (HANDLE_SIZE / 2.0) / zoom;
|
|
257
|
|
258 PolylineSceneLayer::Chain chain;
|
|
259 chain.reserve(4);
|
|
260 chain.push_back(ScenePoint2D(x1, y1));
|
|
261 chain.push_back(ScenePoint2D(x2, y1));
|
|
262 chain.push_back(ScenePoint2D(x2, y2));
|
|
263 chain.push_back(ScenePoint2D(x1, y2));
|
|
264
|
|
265 if (IsHover())
|
|
266 {
|
|
267 polyline.AddChain(chain, true /* closed */, GetHoverColor());
|
|
268 }
|
|
269 else
|
|
270 {
|
|
271 polyline.AddChain(chain, true /* closed */, GetColor());
|
|
272 }
|
|
273 }
|
|
274
|
|
275 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
276 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
277 {
|
|
278 }
|
|
279
|
|
280 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
281 {
|
|
282 SetModified(true);
|
|
283 delta_ = delta;
|
|
284 GetParentAnnotation().SignalMove(*this);
|
|
285 }
|
|
286
|
|
287 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
288 {
|
|
289 SetModified(true);
|
|
290 center_ = center_ + delta;
|
|
291 delta_ = ScenePoint2D(0, 0);
|
|
292 GetParentAnnotation().SignalMove(*this);
|
|
293 }
|
|
294 };
|
|
295
|
|
296
|
|
297 class AnnotationsSceneLayer::Segment : public GeometricPrimitive
|
|
298 {
|
|
299 private:
|
|
300 ScenePoint2D p1_;
|
|
301 ScenePoint2D p2_;
|
|
302 ScenePoint2D delta_;
|
|
303
|
|
304 public:
|
|
305 Segment(Annotation& parentAnnotation,
|
|
306 const ScenePoint2D& p1,
|
|
307 const ScenePoint2D& p2) :
|
|
308 GeometricPrimitive(parentAnnotation, 1), // Can only be selected if no handle matches
|
|
309 p1_(p1),
|
|
310 p2_(p2),
|
|
311 delta_(0, 0)
|
|
312 {
|
|
313 }
|
|
314
|
|
315 void SetPosition(const ScenePoint2D& p1,
|
|
316 const ScenePoint2D& p2)
|
|
317 {
|
|
318 SetModified(true);
|
|
319 p1_ = p1;
|
|
320 p2_ = p2;
|
|
321 delta_ = ScenePoint2D(0, 0);
|
|
322 }
|
|
323
|
|
324 ScenePoint2D GetPosition1() const
|
|
325 {
|
|
326 return p1_ + delta_;
|
|
327 }
|
|
328
|
|
329 ScenePoint2D GetPosition2() const
|
|
330 {
|
|
331 return p2_ + delta_;
|
|
332 }
|
|
333
|
|
334 virtual bool IsHit(const ScenePoint2D& p,
|
|
335 const Scene2D& scene) const
|
|
336 {
|
|
337 const double zoom = scene.GetSceneToCanvasTransform().ComputeZoom();
|
|
338 return (ScenePoint2D::SquaredDistancePtSegment(p1_ + delta_, p2_ + delta_, p) * zoom * zoom <=
|
|
339 (HANDLE_SIZE / 2.0) * (HANDLE_SIZE / 2.0));
|
|
340 }
|
|
341
|
|
342 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
343 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
344 {
|
|
345 PolylineSceneLayer::Chain chain;
|
|
346 chain.reserve(2);
|
|
347 chain.push_back(p1_ + delta_);
|
|
348 chain.push_back(p2_ + delta_);
|
|
349
|
|
350 if (IsHover())
|
|
351 {
|
|
352 polyline.AddChain(chain, false /* closed */, GetHoverColor());
|
|
353 }
|
|
354 else
|
|
355 {
|
|
356 polyline.AddChain(chain, false /* closed */, GetColor());
|
|
357 }
|
|
358 }
|
|
359
|
|
360 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
361 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
362 {
|
|
363 }
|
|
364
|
|
365 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
366 {
|
|
367 SetModified(true);
|
|
368 delta_ = delta;
|
|
369 GetParentAnnotation().SignalMove(*this);
|
|
370 }
|
|
371
|
|
372 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
373 {
|
|
374 SetModified(true);
|
|
375 p1_ = p1_ + delta;
|
|
376 p2_ = p2_ + delta;
|
|
377 delta_ = ScenePoint2D(0, 0);
|
|
378 GetParentAnnotation().SignalMove(*this);
|
|
379 }
|
|
380 };
|
|
381
|
|
382
|
|
383 class AnnotationsSceneLayer::Circle : public GeometricPrimitive
|
|
384 {
|
|
385 private:
|
|
386 ScenePoint2D p1_;
|
|
387 ScenePoint2D p2_;
|
|
388
|
|
389 public:
|
|
390 Circle(Annotation& parentAnnotation,
|
|
391 const ScenePoint2D& p1,
|
|
392 const ScenePoint2D& p2) :
|
|
393 GeometricPrimitive(parentAnnotation, 2),
|
|
394 p1_(p1),
|
|
395 p2_(p2)
|
|
396 {
|
|
397 }
|
|
398
|
|
399 void SetPosition(const ScenePoint2D& p1,
|
|
400 const ScenePoint2D& p2)
|
|
401 {
|
|
402 SetModified(true);
|
|
403 p1_ = p1;
|
|
404 p2_ = p2;
|
|
405 }
|
|
406
|
|
407 ScenePoint2D GetPosition1() const
|
|
408 {
|
|
409 return p1_;
|
|
410 }
|
|
411
|
|
412 ScenePoint2D GetPosition2() const
|
|
413 {
|
|
414 return p2_;
|
|
415 }
|
|
416
|
|
417 virtual bool IsHit(const ScenePoint2D& p,
|
|
418 const Scene2D& scene) const
|
|
419 {
|
|
420 return false;
|
|
421 }
|
|
422
|
|
423 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
424 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
425 {
|
|
426 static unsigned int NUM_SEGMENTS = 128;
|
|
427
|
|
428 ScenePoint2D middle((p1_.GetX() + p2_.GetX()) / 2.0,
|
|
429 (p1_.GetY() + p2_.GetY()) / 2.0);
|
|
430
|
|
431 const double radius = ScenePoint2D::DistancePtPt(middle, p1_);
|
|
432
|
|
433 double increment = 2.0 * PI / static_cast<double>(NUM_SEGMENTS - 1);
|
|
434
|
|
435 PolylineSceneLayer::Chain chain;
|
|
436 chain.reserve(NUM_SEGMENTS);
|
|
437
|
|
438 double theta = 0;
|
|
439 for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
|
|
440 {
|
|
441 chain.push_back(ScenePoint2D(middle.GetX() + radius * cos(theta),
|
|
442 middle.GetY() + radius * sin(theta)));
|
|
443 theta += increment;
|
|
444 }
|
|
445
|
|
446 if (IsHover())
|
|
447 {
|
|
448 polyline.AddChain(chain, false /* closed */, GetHoverColor());
|
|
449 }
|
|
450 else
|
|
451 {
|
|
452 polyline.AddChain(chain, false /* closed */, GetColor());
|
|
453 }
|
|
454 }
|
|
455
|
|
456 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
457 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
458 {
|
|
459 }
|
|
460
|
|
461 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
462 {
|
|
463 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
464 }
|
|
465
|
|
466 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
467 {
|
|
468 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
469 }
|
|
470 };
|
|
471
|
|
472
|
|
473 class AnnotationsSceneLayer::Arc : public GeometricPrimitive
|
|
474 {
|
|
475 private:
|
|
476 ScenePoint2D start_;
|
|
477 ScenePoint2D middle_;
|
|
478 ScenePoint2D end_;
|
|
479 double radius_; // in pixels
|
|
480
|
|
481 void ComputeAngles(double& fullAngle,
|
|
482 double& startAngle,
|
|
483 double& endAngle) const
|
|
484 {
|
|
485 const double x1 = start_.GetX();
|
|
486 const double y1 = start_.GetY();
|
|
487 const double xc = middle_.GetX();
|
|
488 const double yc = middle_.GetY();
|
|
489 const double x2 = end_.GetX();
|
|
490 const double y2 = end_.GetY();
|
|
491
|
|
492 startAngle = atan2(y1 - yc, x1 - xc);
|
|
493 endAngle = atan2(y2 - yc, x2 - xc);
|
|
494
|
|
495 fullAngle = endAngle - startAngle;
|
|
496
|
|
497 while (fullAngle < -PI)
|
|
498 {
|
|
499 fullAngle += 2.0 * PI;
|
|
500 }
|
|
501
|
|
502 while (fullAngle >= PI)
|
|
503 {
|
|
504 fullAngle -= 2.0 * PI;
|
|
505 }
|
|
506 }
|
|
507
|
|
508 public:
|
|
509 Arc(Annotation& parentAnnotation,
|
|
510 const ScenePoint2D& start,
|
|
511 const ScenePoint2D& middle,
|
|
512 const ScenePoint2D& end) :
|
|
513 GeometricPrimitive(parentAnnotation, 2),
|
|
514 start_(start),
|
|
515 middle_(middle),
|
|
516 end_(end),
|
|
517 radius_(20)
|
|
518 {
|
|
519 }
|
|
520
|
|
521 double GetAngle() const
|
|
522 {
|
|
523 double fullAngle, startAngle, endAngle;
|
|
524 ComputeAngles(fullAngle, startAngle, endAngle);
|
|
525 return fullAngle;
|
|
526 }
|
|
527
|
|
528 void SetStart(const ScenePoint2D& p)
|
|
529 {
|
|
530 SetModified(true);
|
|
531 start_ = p;
|
|
532 }
|
|
533
|
|
534 void SetMiddle(const ScenePoint2D& p)
|
|
535 {
|
|
536 SetModified(true);
|
|
537 middle_ = p;
|
|
538 }
|
|
539
|
|
540 void SetEnd(const ScenePoint2D& p)
|
|
541 {
|
|
542 SetModified(true);
|
|
543 end_ = p;
|
|
544 }
|
|
545
|
|
546 virtual bool IsHit(const ScenePoint2D& p,
|
|
547 const Scene2D& scene) const
|
|
548 {
|
|
549 return false;
|
|
550 }
|
|
551
|
|
552 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
553 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
554 {
|
|
555 static unsigned int NUM_SEGMENTS = 64;
|
|
556
|
|
557 const double radius = radius_ / scene.GetSceneToCanvasTransform().ComputeZoom();
|
|
558
|
|
559 double fullAngle, startAngle, endAngle;
|
|
560 ComputeAngles(fullAngle, startAngle, endAngle);
|
|
561
|
|
562 double increment = fullAngle / static_cast<double>(NUM_SEGMENTS - 1);
|
|
563
|
|
564 PolylineSceneLayer::Chain chain;
|
|
565 chain.reserve(NUM_SEGMENTS);
|
|
566
|
|
567 double theta = startAngle;
|
|
568 for (unsigned int i = 0; i < NUM_SEGMENTS; i++)
|
|
569 {
|
|
570 chain.push_back(ScenePoint2D(middle_.GetX() + radius * cos(theta),
|
|
571 middle_.GetY() + radius * sin(theta)));
|
|
572 theta += increment;
|
|
573 }
|
|
574
|
|
575 if (IsHover())
|
|
576 {
|
|
577 polyline.AddChain(chain, false /* closed */, GetHoverColor());
|
|
578 }
|
|
579 else
|
|
580 {
|
|
581 polyline.AddChain(chain, false /* closed */, GetColor());
|
|
582 }
|
|
583 }
|
|
584
|
|
585 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
586 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
587 {
|
|
588 }
|
|
589
|
|
590 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
591 {
|
|
592 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
593 }
|
|
594
|
|
595 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
596 {
|
|
597 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
598 }
|
|
599 };
|
|
600
|
|
601
|
|
602 class AnnotationsSceneLayer::Text : public GeometricPrimitive
|
|
603 {
|
|
604 private:
|
|
605 AnnotationsSceneLayer& that_;
|
|
606 bool first_;
|
|
607 size_t subLayer_;
|
|
608 std::unique_ptr<TextSceneLayer> content_;
|
|
609
|
|
610 public:
|
|
611 Text(AnnotationsSceneLayer& that,
|
|
612 Annotation& parentAnnotation) :
|
|
613 GeometricPrimitive(parentAnnotation, 2),
|
|
614 that_(that),
|
|
615 first_(true)
|
|
616 {
|
|
617 }
|
|
618
|
|
619 virtual ~Text()
|
|
620 {
|
|
621 if (!first_)
|
|
622 {
|
|
623 that_.TagSubLayerToRemove(subLayer_);
|
|
624 }
|
|
625 }
|
|
626
|
|
627 void SetContent(const TextSceneLayer& content)
|
|
628 {
|
|
629 SetModified(true);
|
|
630 content_.reset(dynamic_cast<TextSceneLayer*>(content.Clone()));
|
|
631 }
|
|
632
|
|
633 virtual bool IsHit(const ScenePoint2D& p,
|
|
634 const Scene2D& scene) const
|
|
635 {
|
|
636 return false;
|
|
637 }
|
|
638
|
|
639 virtual void RenderPolylineLayer(PolylineSceneLayer& polyline,
|
|
640 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
641 {
|
|
642 }
|
|
643
|
|
644 virtual void RenderOtherLayers(MacroSceneLayer& macro,
|
|
645 const Scene2D& scene) ORTHANC_OVERRIDE
|
|
646 {
|
|
647 if (content_.get() != NULL)
|
|
648 {
|
|
649 std::unique_ptr<TextSceneLayer> layer(reinterpret_cast<TextSceneLayer*>(content_->Clone()));
|
|
650
|
|
651 layer->SetColor(IsHover() ? GetHoverColor() : GetColor());
|
|
652
|
|
653 if (first_)
|
|
654 {
|
|
655 subLayer_ = macro.AddLayer(layer.release());
|
|
656 first_ = false;
|
|
657 }
|
|
658 else
|
|
659 {
|
|
660 macro.UpdateLayer(subLayer_, layer.release());
|
|
661 }
|
|
662 }
|
|
663 }
|
|
664
|
|
665 virtual void MovePreview(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
666 {
|
|
667 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
668 }
|
|
669
|
|
670 virtual void MoveDone(const ScenePoint2D& delta) ORTHANC_OVERRIDE
|
|
671 {
|
|
672 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); // No hit is possible
|
|
673 }
|
|
674 };
|
|
675
|
|
676
|
|
677 class AnnotationsSceneLayer::EditPrimitiveTracker : public IFlexiblePointerTracker
|
|
678 {
|
|
679 private:
|
|
680 GeometricPrimitive& primitive_;
|
|
681 ScenePoint2D sceneClick_;
|
|
682 AffineTransform2D canvasToScene_;
|
|
683 bool alive_;
|
|
684
|
|
685 public:
|
|
686 EditPrimitiveTracker(GeometricPrimitive& primitive,
|
|
687 const ScenePoint2D& sceneClick,
|
|
688 const AffineTransform2D& canvasToScene) :
|
|
689 primitive_(primitive),
|
|
690 sceneClick_(sceneClick),
|
|
691 canvasToScene_(canvasToScene),
|
|
692 alive_(true)
|
|
693 {
|
|
694 }
|
|
695
|
|
696 virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
697 {
|
|
698 primitive_.MovePreview(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
|
|
699 }
|
|
700
|
|
701 virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
702 {
|
|
703 primitive_.MoveDone(event.GetMainPosition().Apply(canvasToScene_) - sceneClick_);
|
|
704 alive_ = false;
|
|
705 }
|
|
706
|
|
707 virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
708 {
|
|
709 }
|
|
710
|
|
711 virtual bool IsAlive() const ORTHANC_OVERRIDE
|
|
712 {
|
|
713 return alive_;
|
|
714 }
|
|
715
|
|
716 virtual void Cancel() ORTHANC_OVERRIDE
|
|
717 {
|
|
718 primitive_.MoveDone(ScenePoint2D(0, 0));
|
|
719 }
|
|
720 };
|
|
721
|
|
722
|
|
723 class AnnotationsSceneLayer::SegmentAnnotation : public Annotation
|
|
724 {
|
|
725 private:
|
|
726 bool showLabel_;
|
|
727 Handle& handle1_;
|
|
728 Handle& handle2_;
|
|
729 Segment& segment_;
|
|
730 Text& label_;
|
|
731
|
|
732 void UpdateLabel()
|
|
733 {
|
|
734 if (showLabel_)
|
|
735 {
|
|
736 TextSceneLayer content;
|
|
737
|
|
738 double x1 = handle1_.GetCenter().GetX();
|
|
739 double y1 = handle1_.GetCenter().GetY();
|
|
740 double x2 = handle2_.GetCenter().GetX();
|
|
741 double y2 = handle2_.GetCenter().GetY();
|
|
742
|
|
743 // Put the label to the right of the right-most handle
|
|
744 if (x1 < x2)
|
|
745 {
|
|
746 content.SetPosition(x2, y2);
|
|
747 }
|
|
748 else
|
|
749 {
|
|
750 content.SetPosition(x1, y1);
|
|
751 }
|
|
752
|
|
753 content.SetAnchor(BitmapAnchor_CenterLeft);
|
|
754 content.SetBorder(10);
|
|
755
|
|
756 double dx = x1 - x2;
|
|
757 double dy = y1 - y2;
|
|
758 char buf[32];
|
|
759 sprintf(buf, "%0.2f cm", sqrt(dx * dx + dy * dy) / 10.0);
|
|
760 content.SetText(buf);
|
|
761
|
|
762 label_.SetContent(content);
|
|
763 }
|
|
764 }
|
|
765
|
|
766 public:
|
|
767 SegmentAnnotation(AnnotationsSceneLayer& that,
|
|
768 bool showLabel,
|
|
769 const ScenePoint2D& p1,
|
|
770 const ScenePoint2D& p2) :
|
|
771 Annotation(that),
|
|
772 showLabel_(showLabel),
|
|
773 handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
|
|
774 handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
|
|
775 segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
|
|
776 label_(AddTypedPrimitive<Text>(new Text(that, *this)))
|
|
777 {
|
|
778 label_.SetColor(Color(255, 0, 0));
|
|
779 UpdateLabel();
|
|
780 }
|
|
781
|
|
782 Handle& GetHandle1() const
|
|
783 {
|
|
784 return handle1_;
|
|
785 }
|
|
786
|
|
787 Handle& GetHandle2() const
|
|
788 {
|
|
789 return handle2_;
|
|
790 }
|
|
791
|
|
792 virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
|
|
793 {
|
|
794 if (&primitive == &handle1_ ||
|
|
795 &primitive == &handle2_)
|
|
796 {
|
|
797 segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
|
|
798 }
|
|
799 else if (&primitive == &segment_)
|
|
800 {
|
|
801 handle1_.SetCenter(segment_.GetPosition1());
|
|
802 handle2_.SetCenter(segment_.GetPosition2());
|
|
803 }
|
|
804
|
|
805 UpdateLabel();
|
|
806 }
|
|
807
|
|
808 virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
|
|
809 {
|
|
810 target = Json::objectValue;
|
|
811 target[KEY_TYPE] = VALUE_SEGMENT;
|
|
812 target[KEY_X1] = handle1_.GetCenter().GetX();
|
|
813 target[KEY_Y1] = handle1_.GetCenter().GetY();
|
|
814 target[KEY_X2] = handle2_.GetCenter().GetX();
|
|
815 target[KEY_Y2] = handle2_.GetCenter().GetY();
|
|
816 }
|
|
817
|
|
818 static void Unserialize(AnnotationsSceneLayer& target,
|
|
819 const Json::Value& source)
|
|
820 {
|
|
821 if (source.isMember(KEY_X1) &&
|
|
822 source.isMember(KEY_Y1) &&
|
|
823 source.isMember(KEY_X2) &&
|
|
824 source.isMember(KEY_Y2) &&
|
|
825 source[KEY_X1].isNumeric() &&
|
|
826 source[KEY_Y1].isNumeric() &&
|
|
827 source[KEY_X2].isNumeric() &&
|
|
828 source[KEY_Y2].isNumeric())
|
|
829 {
|
|
830 new SegmentAnnotation(target, true,
|
|
831 ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
|
|
832 ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
|
|
833 }
|
|
834 else
|
|
835 {
|
|
836 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an segment annotation");
|
|
837 }
|
|
838 }
|
|
839 };
|
|
840
|
|
841
|
|
842 class AnnotationsSceneLayer::AngleAnnotation : public Annotation
|
|
843 {
|
|
844 private:
|
|
845 Handle& startHandle_;
|
|
846 Handle& middleHandle_;
|
|
847 Handle& endHandle_;
|
|
848 Segment& segment1_;
|
|
849 Segment& segment2_;
|
|
850 Arc& arc_;
|
|
851 Text& label_;
|
|
852
|
|
853 void UpdateLabel()
|
|
854 {
|
|
855 TextSceneLayer content;
|
|
856
|
|
857 const double x1 = startHandle_.GetCenter().GetX();
|
|
858 const double x2 = middleHandle_.GetCenter().GetX();
|
|
859 const double y2 = middleHandle_.GetCenter().GetY();
|
|
860 const double x3 = endHandle_.GetCenter().GetX();
|
|
861
|
|
862 if (x2 < x1 &&
|
|
863 x2 < x3)
|
|
864 {
|
|
865 content.SetAnchor(BitmapAnchor_CenterRight);
|
|
866 }
|
|
867 else
|
|
868 {
|
|
869 content.SetAnchor(BitmapAnchor_CenterLeft);
|
|
870 }
|
|
871
|
|
872 content.SetPosition(x2, y2);
|
|
873 content.SetBorder(10);
|
|
874
|
|
875 char buf[32];
|
|
876 sprintf(buf, "%.01f%c%c", std::abs(arc_.GetAngle()) / PI * 180.0,
|
|
877 0xc2, 0xb0 /* two bytes corresponding to degree symbol in UTF-8 */);
|
|
878 content.SetText(buf);
|
|
879
|
|
880 label_.SetContent(content);
|
|
881 }
|
|
882
|
|
883 public:
|
|
884 AngleAnnotation(AnnotationsSceneLayer& that,
|
|
885 const ScenePoint2D& start,
|
|
886 const ScenePoint2D& middle,
|
|
887 const ScenePoint2D& end) :
|
|
888 Annotation(that),
|
|
889 startHandle_(AddTypedPrimitive<Handle>(new Handle(*this, start))),
|
|
890 middleHandle_(AddTypedPrimitive<Handle>(new Handle(*this, middle))),
|
|
891 endHandle_(AddTypedPrimitive<Handle>(new Handle(*this, end))),
|
|
892 segment1_(AddTypedPrimitive<Segment>(new Segment(*this, start, middle))),
|
|
893 segment2_(AddTypedPrimitive<Segment>(new Segment(*this, middle, end))),
|
|
894 arc_(AddTypedPrimitive<Arc>(new Arc(*this, start, middle, end))),
|
|
895 label_(AddTypedPrimitive<Text>(new Text(that, *this)))
|
|
896 {
|
|
897 label_.SetColor(Color(255, 0, 0));
|
|
898 UpdateLabel();
|
|
899 }
|
|
900
|
|
901 Handle& GetEndHandle() const
|
|
902 {
|
|
903 return endHandle_;
|
|
904 }
|
|
905
|
|
906 virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
|
|
907 {
|
|
908 if (&primitive == &startHandle_)
|
|
909 {
|
|
910 segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
|
|
911 arc_.SetStart(startHandle_.GetCenter());
|
|
912 }
|
|
913 else if (&primitive == &middleHandle_)
|
|
914 {
|
|
915 segment1_.SetPosition(startHandle_.GetCenter(), middleHandle_.GetCenter());
|
|
916 segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
|
|
917 arc_.SetMiddle(middleHandle_.GetCenter());
|
|
918 }
|
|
919 else if (&primitive == &endHandle_)
|
|
920 {
|
|
921 segment2_.SetPosition(middleHandle_.GetCenter(), endHandle_.GetCenter());
|
|
922 arc_.SetEnd(endHandle_.GetCenter());
|
|
923 }
|
|
924 else if (&primitive == &segment1_)
|
|
925 {
|
|
926 startHandle_.SetCenter(segment1_.GetPosition1());
|
|
927 middleHandle_.SetCenter(segment1_.GetPosition2());
|
|
928 segment2_.SetPosition(segment1_.GetPosition2(), segment2_.GetPosition2());
|
|
929 arc_.SetStart(segment1_.GetPosition1());
|
|
930 arc_.SetMiddle(segment1_.GetPosition2());
|
|
931 }
|
|
932 else if (&primitive == &segment2_)
|
|
933 {
|
|
934 middleHandle_.SetCenter(segment2_.GetPosition1());
|
|
935 endHandle_.SetCenter(segment2_.GetPosition2());
|
|
936 segment1_.SetPosition(segment1_.GetPosition1(), segment2_.GetPosition1());
|
|
937 arc_.SetMiddle(segment2_.GetPosition1());
|
|
938 arc_.SetEnd(segment2_.GetPosition2());
|
|
939 }
|
|
940
|
|
941 UpdateLabel();
|
|
942 }
|
|
943
|
|
944 virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
|
|
945 {
|
|
946 target = Json::objectValue;
|
|
947 target[KEY_TYPE] = VALUE_ANGLE;
|
|
948 target[KEY_X1] = startHandle_.GetCenter().GetX();
|
|
949 target[KEY_Y1] = startHandle_.GetCenter().GetY();
|
|
950 target[KEY_X2] = middleHandle_.GetCenter().GetX();
|
|
951 target[KEY_Y2] = middleHandle_.GetCenter().GetY();
|
|
952 target[KEY_X3] = endHandle_.GetCenter().GetX();
|
|
953 target[KEY_Y3] = endHandle_.GetCenter().GetY();
|
|
954 }
|
|
955
|
|
956 static void Unserialize(AnnotationsSceneLayer& target,
|
|
957 const Json::Value& source)
|
|
958 {
|
|
959 if (source.isMember(KEY_X1) &&
|
|
960 source.isMember(KEY_Y1) &&
|
|
961 source.isMember(KEY_X2) &&
|
|
962 source.isMember(KEY_Y2) &&
|
|
963 source.isMember(KEY_X3) &&
|
|
964 source.isMember(KEY_Y3) &&
|
|
965 source[KEY_X1].isNumeric() &&
|
|
966 source[KEY_Y1].isNumeric() &&
|
|
967 source[KEY_X2].isNumeric() &&
|
|
968 source[KEY_Y2].isNumeric() &&
|
|
969 source[KEY_X3].isNumeric() &&
|
|
970 source[KEY_Y3].isNumeric())
|
|
971 {
|
|
972 new AngleAnnotation(target,
|
|
973 ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
|
|
974 ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()),
|
|
975 ScenePoint2D(source[KEY_X3].asDouble(), source[KEY_Y3].asDouble()));
|
|
976 }
|
|
977 else
|
|
978 {
|
|
979 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an angle annotation");
|
|
980 }
|
|
981 }
|
|
982 };
|
|
983
|
|
984
|
|
985 class AnnotationsSceneLayer::CircleAnnotation : public Annotation
|
|
986 {
|
|
987 private:
|
|
988 Handle& handle1_;
|
|
989 Handle& handle2_;
|
|
990 Segment& segment_;
|
|
991 Circle& circle_;
|
|
992 Text& label_;
|
|
993
|
|
994 void UpdateLabel()
|
|
995 {
|
|
996 TextSceneLayer content;
|
|
997
|
|
998 double x1 = handle1_.GetCenter().GetX();
|
|
999 double y1 = handle1_.GetCenter().GetY();
|
|
1000 double x2 = handle2_.GetCenter().GetX();
|
|
1001 double y2 = handle2_.GetCenter().GetY();
|
|
1002
|
|
1003 // Put the label to the right of the right-most handle
|
|
1004 if (x1 < x2)
|
|
1005 {
|
|
1006 content.SetPosition(x2, y2);
|
|
1007 }
|
|
1008 else
|
|
1009 {
|
|
1010 content.SetPosition(x1, y1);
|
|
1011 }
|
|
1012
|
|
1013 content.SetAnchor(BitmapAnchor_CenterLeft);
|
|
1014 content.SetBorder(10);
|
|
1015
|
|
1016 double dx = x1 - x2;
|
|
1017 double dy = y1 - y2;
|
|
1018 double diameter = sqrt(dx * dx + dy * dy); // in millimeters
|
|
1019
|
|
1020 double area = PI * diameter * diameter / 4.0;
|
|
1021
|
|
1022 char buf[32];
|
|
1023 sprintf(buf, "%0.2f cm\n%0.2f cm%c%c",
|
|
1024 diameter / 10.0,
|
|
1025 area / 100.0,
|
|
1026 0xc2, 0xb2 /* two bytes corresponding to two power in UTF-8 */);
|
|
1027 content.SetText(buf);
|
|
1028
|
|
1029 label_.SetContent(content);
|
|
1030 }
|
|
1031
|
|
1032 public:
|
|
1033 CircleAnnotation(AnnotationsSceneLayer& that,
|
|
1034 const ScenePoint2D& p1,
|
|
1035 const ScenePoint2D& p2) :
|
|
1036 Annotation(that),
|
|
1037 handle1_(AddTypedPrimitive<Handle>(new Handle(*this, p1))),
|
|
1038 handle2_(AddTypedPrimitive<Handle>(new Handle(*this, p2))),
|
|
1039 segment_(AddTypedPrimitive<Segment>(new Segment(*this, p1, p2))),
|
|
1040 circle_(AddTypedPrimitive<Circle>(new Circle(*this, p1, p2))),
|
|
1041 label_(AddTypedPrimitive<Text>(new Text(that, *this)))
|
|
1042 {
|
|
1043 label_.SetColor(Color(255, 0, 0));
|
|
1044 UpdateLabel();
|
|
1045 }
|
|
1046
|
|
1047 Handle& GetHandle2() const
|
|
1048 {
|
|
1049 return handle2_;
|
|
1050 }
|
|
1051
|
|
1052 virtual void SignalMove(GeometricPrimitive& primitive) ORTHANC_OVERRIDE
|
|
1053 {
|
|
1054 if (&primitive == &handle1_ ||
|
|
1055 &primitive == &handle2_)
|
|
1056 {
|
|
1057 segment_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
|
|
1058 circle_.SetPosition(handle1_.GetCenter(), handle2_.GetCenter());
|
|
1059 }
|
|
1060 else if (&primitive == &segment_)
|
|
1061 {
|
|
1062 handle1_.SetCenter(segment_.GetPosition1());
|
|
1063 handle2_.SetCenter(segment_.GetPosition2());
|
|
1064 circle_.SetPosition(segment_.GetPosition1(), segment_.GetPosition2());
|
|
1065 }
|
|
1066
|
|
1067 UpdateLabel();
|
|
1068 }
|
|
1069
|
|
1070 virtual void Serialize(Json::Value& target) ORTHANC_OVERRIDE
|
|
1071 {
|
|
1072 target = Json::objectValue;
|
|
1073 target[KEY_TYPE] = VALUE_CIRCLE;
|
|
1074 target[KEY_X1] = handle1_.GetCenter().GetX();
|
|
1075 target[KEY_Y1] = handle1_.GetCenter().GetY();
|
|
1076 target[KEY_X2] = handle2_.GetCenter().GetX();
|
|
1077 target[KEY_Y2] = handle2_.GetCenter().GetY();
|
|
1078 }
|
|
1079
|
|
1080 static void Unserialize(AnnotationsSceneLayer& target,
|
|
1081 const Json::Value& source)
|
|
1082 {
|
|
1083 if (source.isMember(KEY_X1) &&
|
|
1084 source.isMember(KEY_Y1) &&
|
|
1085 source.isMember(KEY_X2) &&
|
|
1086 source.isMember(KEY_Y2) &&
|
|
1087 source[KEY_X1].isNumeric() &&
|
|
1088 source[KEY_Y1].isNumeric() &&
|
|
1089 source[KEY_X2].isNumeric() &&
|
|
1090 source[KEY_Y2].isNumeric())
|
|
1091 {
|
|
1092 new CircleAnnotation(target,
|
|
1093 ScenePoint2D(source[KEY_X1].asDouble(), source[KEY_Y1].asDouble()),
|
|
1094 ScenePoint2D(source[KEY_X2].asDouble(), source[KEY_Y2].asDouble()));
|
|
1095 }
|
|
1096 else
|
|
1097 {
|
|
1098 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize an circle annotation");
|
|
1099 }
|
|
1100 }
|
|
1101 };
|
|
1102
|
|
1103
|
|
1104 class AnnotationsSceneLayer::CreateSegmentOrCircleTracker : public IFlexiblePointerTracker
|
|
1105 {
|
|
1106 private:
|
|
1107 AnnotationsSceneLayer& that_;
|
|
1108 Annotation* annotation_;
|
|
1109 AffineTransform2D canvasToScene_;
|
|
1110 Handle* handle2_;
|
|
1111
|
|
1112 public:
|
|
1113 CreateSegmentOrCircleTracker(AnnotationsSceneLayer& that,
|
|
1114 bool isCircle,
|
|
1115 const ScenePoint2D& sceneClick,
|
|
1116 const AffineTransform2D& canvasToScene) :
|
|
1117 that_(that),
|
|
1118 annotation_(NULL),
|
|
1119 canvasToScene_(canvasToScene),
|
|
1120 handle2_(NULL)
|
|
1121 {
|
|
1122 if (isCircle)
|
|
1123 {
|
|
1124 annotation_ = new CircleAnnotation(that, sceneClick, sceneClick);
|
|
1125 handle2_ = &dynamic_cast<CircleAnnotation*>(annotation_)->GetHandle2();
|
|
1126 }
|
|
1127 else
|
|
1128 {
|
|
1129 annotation_ = new SegmentAnnotation(that, true /* show label */, sceneClick, sceneClick);
|
|
1130 handle2_ = &dynamic_cast<SegmentAnnotation*>(annotation_)->GetHandle2();
|
|
1131 }
|
|
1132
|
|
1133 assert(annotation_ != NULL &&
|
|
1134 handle2_ != NULL);
|
|
1135 }
|
|
1136
|
|
1137 virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1138 {
|
|
1139 if (annotation_ != NULL)
|
|
1140 {
|
|
1141 assert(handle2_ != NULL);
|
|
1142 handle2_->SetCenter(event.GetMainPosition().Apply(canvasToScene_));
|
|
1143 annotation_->SignalMove(*handle2_);
|
|
1144 }
|
|
1145 }
|
|
1146
|
|
1147 virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1148 {
|
|
1149 annotation_ = NULL; // IsAlive() becomes false
|
|
1150
|
|
1151 that_.BroadcastMessage(AnnotationAddedMessage(that_));
|
|
1152 }
|
|
1153
|
|
1154 virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1155 {
|
|
1156 }
|
|
1157
|
|
1158 virtual bool IsAlive() const ORTHANC_OVERRIDE
|
|
1159 {
|
|
1160 return (annotation_ != NULL);
|
|
1161 }
|
|
1162
|
|
1163 virtual void Cancel() ORTHANC_OVERRIDE
|
|
1164 {
|
|
1165 if (annotation_ != NULL)
|
|
1166 {
|
|
1167 that_.DeleteAnnotation(annotation_);
|
|
1168 annotation_ = NULL;
|
|
1169 }
|
|
1170 }
|
|
1171 };
|
|
1172
|
|
1173
|
|
1174 class AnnotationsSceneLayer::CreateAngleTracker : public IFlexiblePointerTracker
|
|
1175 {
|
|
1176 private:
|
|
1177 AnnotationsSceneLayer& that_;
|
|
1178 SegmentAnnotation* segment_;
|
|
1179 AngleAnnotation* angle_;
|
|
1180 AffineTransform2D canvasToScene_;
|
|
1181
|
|
1182 public:
|
|
1183 CreateAngleTracker(AnnotationsSceneLayer& that,
|
|
1184 const ScenePoint2D& sceneClick,
|
|
1185 const AffineTransform2D& canvasToScene) :
|
|
1186 that_(that),
|
|
1187 segment_(NULL),
|
|
1188 angle_(NULL),
|
|
1189 canvasToScene_(canvasToScene)
|
|
1190 {
|
|
1191 segment_ = new SegmentAnnotation(that, false /* no length label */, sceneClick, sceneClick);
|
|
1192 }
|
|
1193
|
|
1194 virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1195 {
|
|
1196 if (segment_ != NULL)
|
|
1197 {
|
|
1198 segment_->GetHandle2().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
|
|
1199 segment_->SignalMove(segment_->GetHandle2());
|
|
1200 }
|
|
1201
|
|
1202 if (angle_ != NULL)
|
|
1203 {
|
|
1204 angle_->GetEndHandle().SetCenter(event.GetMainPosition().Apply(canvasToScene_));
|
|
1205 angle_->SignalMove(angle_->GetEndHandle());
|
|
1206 }
|
|
1207 }
|
|
1208
|
|
1209 virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1210 {
|
|
1211 if (segment_ != NULL)
|
|
1212 {
|
|
1213 // End of first step: The first segment is available, now create the angle
|
|
1214
|
|
1215 angle_ = new AngleAnnotation(that_, segment_->GetHandle1().GetCenter(),
|
|
1216 segment_->GetHandle2().GetCenter(),
|
|
1217 segment_->GetHandle2().GetCenter());
|
|
1218
|
|
1219 that_.DeleteAnnotation(segment_);
|
|
1220 segment_ = NULL;
|
|
1221 }
|
|
1222 else
|
|
1223 {
|
|
1224 angle_ = NULL; // IsAlive() becomes false
|
|
1225
|
|
1226 that_.BroadcastMessage(AnnotationAddedMessage(that_));
|
|
1227 }
|
|
1228 }
|
|
1229
|
|
1230 virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1231 {
|
|
1232 }
|
|
1233
|
|
1234 virtual bool IsAlive() const ORTHANC_OVERRIDE
|
|
1235 {
|
|
1236 return (segment_ != NULL ||
|
|
1237 angle_ != NULL);
|
|
1238 }
|
|
1239
|
|
1240 virtual void Cancel() ORTHANC_OVERRIDE
|
|
1241 {
|
|
1242 if (segment_ != NULL)
|
|
1243 {
|
|
1244 that_.DeleteAnnotation(segment_);
|
|
1245 segment_ = NULL;
|
|
1246 }
|
|
1247
|
|
1248 if (angle_ != NULL)
|
|
1249 {
|
|
1250 that_.DeleteAnnotation(angle_);
|
|
1251 angle_ = NULL;
|
|
1252 }
|
|
1253 }
|
|
1254 };
|
|
1255
|
|
1256
|
|
1257 // Dummy tracker that is only used for deletion, in order to warn
|
|
1258 // the caller that the mouse action was taken into consideration
|
|
1259 class AnnotationsSceneLayer::EraseTracker : public IFlexiblePointerTracker
|
|
1260 {
|
|
1261 public:
|
|
1262 EraseTracker()
|
|
1263 {
|
|
1264 }
|
|
1265
|
|
1266 virtual void PointerMove(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1267 {
|
|
1268 }
|
|
1269
|
|
1270 virtual void PointerUp(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1271 {
|
|
1272 }
|
|
1273
|
|
1274 virtual void PointerDown(const PointerEvent& event) ORTHANC_OVERRIDE
|
|
1275 {
|
|
1276 }
|
|
1277
|
|
1278 virtual bool IsAlive() const ORTHANC_OVERRIDE
|
|
1279 {
|
|
1280 return false;
|
|
1281 }
|
|
1282
|
|
1283 virtual void Cancel() ORTHANC_OVERRIDE
|
|
1284 {
|
|
1285 }
|
|
1286 };
|
|
1287
|
|
1288
|
|
1289 void AnnotationsSceneLayer::AddAnnotation(Annotation* annotation)
|
|
1290 {
|
|
1291 assert(annotation != NULL);
|
|
1292 assert(annotations_.find(annotation) == annotations_.end());
|
|
1293 annotations_.insert(annotation);
|
|
1294 }
|
|
1295
|
|
1296
|
|
1297 void AnnotationsSceneLayer::DeleteAnnotation(Annotation* annotation)
|
|
1298 {
|
|
1299 if (annotation != NULL)
|
|
1300 {
|
|
1301 assert(annotations_.find(annotation) != annotations_.end());
|
|
1302 annotations_.erase(annotation);
|
|
1303 delete annotation;
|
|
1304 }
|
|
1305 }
|
|
1306
|
|
1307
|
|
1308 void AnnotationsSceneLayer::DeletePrimitive(GeometricPrimitive* primitive)
|
|
1309 {
|
|
1310 if (primitive != NULL)
|
|
1311 {
|
|
1312 assert(primitives_.find(primitive) != primitives_.end());
|
|
1313 primitives_.erase(primitive);
|
|
1314 delete primitive;
|
|
1315 }
|
|
1316 }
|
|
1317
|
|
1318
|
|
1319 void AnnotationsSceneLayer::TagSubLayerToRemove(size_t subLayerIndex)
|
|
1320 {
|
|
1321 assert(subLayersToRemove_.find(subLayerIndex) == subLayersToRemove_.end());
|
|
1322 subLayersToRemove_.insert(subLayerIndex);
|
|
1323 }
|
|
1324
|
|
1325
|
|
1326 AnnotationsSceneLayer::AnnotationsSceneLayer(size_t macroLayerIndex) :
|
|
1327 activeTool_(Tool_Edit),
|
|
1328 macroLayerIndex_(macroLayerIndex),
|
|
1329 polylineSubLayer_(0) // dummy initialization
|
|
1330 {
|
|
1331 annotations_.insert(new SegmentAnnotation(*this, true /* show label */, ScenePoint2D(0, 0), ScenePoint2D(100, 100)));
|
|
1332 annotations_.insert(new AngleAnnotation(*this, ScenePoint2D(100, 50), ScenePoint2D(150, 40), ScenePoint2D(200, 50)));
|
|
1333 annotations_.insert(new CircleAnnotation(*this, ScenePoint2D(50, 200), ScenePoint2D(100, 250)));
|
|
1334 }
|
|
1335
|
|
1336
|
|
1337 void AnnotationsSceneLayer::Clear()
|
|
1338 {
|
|
1339 for (Annotations::iterator it = annotations_.begin(); it != annotations_.end(); ++it)
|
|
1340 {
|
|
1341 assert(*it != NULL);
|
|
1342 delete *it;
|
|
1343 }
|
|
1344
|
|
1345 annotations_.clear();
|
|
1346 }
|
|
1347
|
|
1348
|
|
1349 void AnnotationsSceneLayer::Render(Scene2D& scene)
|
|
1350 {
|
|
1351 MacroSceneLayer* macro = NULL;
|
|
1352
|
|
1353 if (scene.HasLayer(macroLayerIndex_))
|
|
1354 {
|
|
1355 macro = &dynamic_cast<MacroSceneLayer&>(scene.GetLayer(macroLayerIndex_));
|
|
1356 }
|
|
1357 else
|
|
1358 {
|
|
1359 macro = &dynamic_cast<MacroSceneLayer&>(scene.SetLayer(macroLayerIndex_, new MacroSceneLayer));
|
|
1360 polylineSubLayer_ = macro->AddLayer(new PolylineSceneLayer);
|
|
1361 }
|
|
1362
|
|
1363 for (SubLayers::const_iterator it = subLayersToRemove_.begin(); it != subLayersToRemove_.end(); ++it)
|
|
1364 {
|
|
1365 assert(macro->HasLayer(*it));
|
|
1366 macro->DeleteLayer(*it);
|
|
1367 }
|
|
1368
|
|
1369 subLayersToRemove_.clear();
|
|
1370
|
|
1371 std::unique_ptr<PolylineSceneLayer> polyline(new PolylineSceneLayer);
|
|
1372
|
|
1373 for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
|
|
1374 {
|
|
1375 assert(*it != NULL);
|
|
1376 GeometricPrimitive& primitive = **it;
|
|
1377
|
|
1378 primitive.RenderPolylineLayer(*polyline, scene);
|
|
1379
|
|
1380 if (primitive.IsModified())
|
|
1381 {
|
|
1382 primitive.RenderOtherLayers(*macro, scene);
|
|
1383 primitive.SetModified(false);
|
|
1384 }
|
|
1385 }
|
|
1386
|
|
1387 macro->UpdateLayer(polylineSubLayer_, polyline.release());
|
|
1388 }
|
|
1389
|
|
1390
|
|
1391 bool AnnotationsSceneLayer::ClearHover()
|
|
1392 {
|
|
1393 bool needsRefresh = false;
|
|
1394
|
|
1395 for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
|
|
1396 {
|
|
1397 assert(*it != NULL);
|
|
1398 if ((*it)->IsHover())
|
|
1399 {
|
|
1400 (*it)->SetHover(false);
|
|
1401 needsRefresh = true;
|
|
1402 }
|
|
1403 }
|
|
1404
|
|
1405 return needsRefresh;
|
|
1406 }
|
|
1407
|
|
1408
|
|
1409 bool AnnotationsSceneLayer::SetMouseHover(const ScenePoint2D& p,
|
|
1410 const Scene2D& scene)
|
|
1411 {
|
|
1412 if (activeTool_ == Tool_None)
|
|
1413 {
|
|
1414 return ClearHover();
|
|
1415 }
|
|
1416 else
|
|
1417 {
|
|
1418 bool needsRefresh = false;
|
|
1419
|
|
1420 const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
|
|
1421
|
|
1422 for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
|
|
1423 {
|
|
1424 assert(*it != NULL);
|
|
1425 bool hover = (*it)->IsHit(s, scene);
|
|
1426
|
|
1427 if ((*it)->IsHover() != hover)
|
|
1428 {
|
|
1429 needsRefresh = true;
|
|
1430 }
|
|
1431
|
|
1432 (*it)->SetHover(hover);
|
|
1433 }
|
|
1434
|
|
1435 return needsRefresh;
|
|
1436 }
|
|
1437 }
|
|
1438
|
|
1439
|
|
1440 IFlexiblePointerTracker* AnnotationsSceneLayer::CreateTracker(const ScenePoint2D& p,
|
|
1441 const Scene2D& scene)
|
|
1442 {
|
|
1443 if (activeTool_ == Tool_None)
|
|
1444 {
|
|
1445 return NULL;
|
|
1446 }
|
|
1447 else
|
|
1448 {
|
|
1449 const ScenePoint2D s = p.Apply(scene.GetCanvasToSceneTransform());
|
|
1450
|
|
1451 GeometricPrimitive* bestHit = NULL;
|
|
1452
|
|
1453 for (GeometricPrimitives::iterator it = primitives_.begin(); it != primitives_.end(); ++it)
|
|
1454 {
|
|
1455 assert(*it != NULL);
|
|
1456 if ((*it)->IsHit(s, scene))
|
|
1457 {
|
|
1458 if (bestHit == NULL ||
|
|
1459 bestHit->GetDepth() > (*it)->GetDepth())
|
|
1460 {
|
|
1461 bestHit = *it;
|
|
1462 }
|
|
1463 }
|
|
1464 }
|
|
1465
|
|
1466 if (bestHit != NULL)
|
|
1467 {
|
|
1468 if (activeTool_ == Tool_Erase)
|
|
1469 {
|
|
1470 DeleteAnnotation(&bestHit->GetParentAnnotation());
|
|
1471 BroadcastMessage(AnnotationRemovedMessage(*this));
|
|
1472 return new EraseTracker;
|
|
1473 }
|
|
1474 else
|
|
1475 {
|
|
1476 return new EditPrimitiveTracker(*bestHit, s, scene.GetCanvasToSceneTransform());
|
|
1477 }
|
|
1478 }
|
|
1479 else
|
|
1480 {
|
|
1481 switch (activeTool_)
|
|
1482 {
|
|
1483 case Tool_Segment:
|
|
1484 return new CreateSegmentOrCircleTracker(*this, false /* segment */, s, scene.GetCanvasToSceneTransform());
|
|
1485
|
|
1486 case Tool_Circle:
|
|
1487 return new CreateSegmentOrCircleTracker(*this, true /* circle */, s, scene.GetCanvasToSceneTransform());
|
|
1488
|
|
1489 case Tool_Angle:
|
|
1490 return new CreateAngleTracker(*this, s, scene.GetCanvasToSceneTransform());
|
|
1491
|
|
1492 default:
|
|
1493 return NULL;
|
|
1494 }
|
|
1495 }
|
|
1496 }
|
|
1497 }
|
|
1498
|
|
1499
|
|
1500 void AnnotationsSceneLayer::Serialize(Json::Value& target) const
|
|
1501 {
|
|
1502 Json::Value annotations = Json::arrayValue;
|
|
1503
|
|
1504 for (Annotations::const_iterator it = annotations_.begin(); it != annotations_.end(); ++it)
|
|
1505 {
|
|
1506 assert(*it != NULL);
|
|
1507
|
|
1508 Json::Value item;
|
|
1509 (*it)->Serialize(item);
|
|
1510 annotations.append(item);
|
|
1511 }
|
|
1512
|
|
1513 target = Json::objectValue;
|
|
1514 target[KEY_ANNOTATIONS] = annotations;
|
|
1515 }
|
|
1516
|
|
1517
|
|
1518 void AnnotationsSceneLayer::Unserialize(const Json::Value& serialized)
|
|
1519 {
|
|
1520 Clear();
|
|
1521
|
|
1522 if (serialized.type() != Json::objectValue ||
|
|
1523 !serialized.isMember(KEY_ANNOTATIONS) ||
|
|
1524 serialized[KEY_ANNOTATIONS].type() != Json::arrayValue)
|
|
1525 {
|
|
1526 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize a set of annotations");
|
|
1527 }
|
|
1528
|
|
1529 const Json::Value& annotations = serialized[KEY_ANNOTATIONS];
|
|
1530
|
|
1531 for (Json::Value::ArrayIndex i = 0; i < annotations.size(); i++)
|
|
1532 {
|
|
1533 if (annotations[i].type() != Json::objectValue ||
|
|
1534 !annotations[i].isMember(KEY_TYPE) ||
|
|
1535 annotations[i][KEY_TYPE].type() != Json::stringValue)
|
|
1536 {
|
|
1537 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
1538 }
|
|
1539
|
|
1540 const std::string& type = annotations[i][KEY_TYPE].asString();
|
|
1541
|
|
1542 if (type == VALUE_ANGLE)
|
|
1543 {
|
|
1544 AngleAnnotation::Unserialize(*this, annotations[i]);
|
|
1545 }
|
|
1546 else if (type == VALUE_CIRCLE)
|
|
1547 {
|
|
1548 CircleAnnotation::Unserialize(*this, annotations[i]);
|
|
1549 }
|
|
1550 else if (type == VALUE_SEGMENT)
|
|
1551 {
|
|
1552 SegmentAnnotation::Unserialize(*this, annotations[i]);
|
|
1553 }
|
|
1554 else
|
|
1555 {
|
|
1556 LOG(ERROR) << "Cannot unserialize unknown type of annotation: " << type;
|
|
1557 }
|
|
1558 }
|
|
1559 }
|
|
1560 }
|