source: git/src/model.cc@ 76cf7f1

RELEASE/1.2 debug-ci debug-ci-sanitisers faster-cavernlog log-select main stereo-2025 walls-data walls-data-hanging-as-warning warn-only-for-hanging-survey
Last change on this file since 76cf7f1 was 76cf7f1, checked in by Olly Betts <olly@…>, 7 years ago

Fix rotated exports

When exporting to a format where we support rotation in the horizontal
plane (such as SVG), the rotation was incorrectly applied to cross
section data (except for the default rotation of zero).

Fixes #108, reported by Richard Knapp.

  • Property mode set to 100644
File size: 19.8 KB
Line 
1//
2// model.cc
3//
4// Cave survey model.
5//
6// Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7// Copyright (C) 2001-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018,2019 Olly Betts
8// Copyright (C) 2005 Martin Green
9//
10// This program is free software; you can redistribute it and/or modify
11// it under the terms of the GNU General Public License as published by
12// the Free Software Foundation; either version 2 of the License, or
13// (at your option) any later version.
14//
15// This program is distributed in the hope that it will be useful,
16// but WITHOUT ANY WARRANTY; without even the implied warranty of
17// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18// GNU General Public License for more details.
19//
20// You should have received a copy of the GNU General Public License
21// along with this program; if not, write to the Free Software
22// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23//
24
25#ifdef HAVE_CONFIG_H
26#include <config.h>
27#endif
28
29#include "model.h"
30
31#include "img_hosted.h"
32#include "useful.h"
33
34#include <cfloat>
35#include <map>
36
37using namespace std;
38
39const static int img2aven_tab[] = {
40#include "img2aven.h"
41};
42
43inline int
44img2aven(int flags)
45{
46 flags &= (sizeof(img2aven_tab) / sizeof(img2aven_tab[0]) - 1);
47 return img2aven_tab[flags];
48}
49
50int Model::Load(const wxString& file, const wxString& prefix)
51{
52 // Load the processed survey data.
53 img* survey = img_read_stream_survey(wxFopen(file, wxT("rb")),
54 fclose,
55 file.c_str(),
56 prefix.utf8_str());
57 if (!survey) {
58 return img_error2msg(img_error());
59 }
60
61 m_IsExtendedElevation = survey->is_extended_elevation;
62
63 // Create a list of all the leg vertices, counting them and finding the
64 // extent of the survey at the same time.
65
66 m_NumFixedPts = 0;
67 m_NumExportedPts = 0;
68 m_NumEntrances = 0;
69 m_HasUndergroundLegs = false;
70 m_HasSplays = false;
71 m_HasDupes = false;
72 m_HasSurfaceLegs = false;
73 m_HasErrorInformation = false;
74
75 // FIXME: discard existing presentation? ask user about saving if we do!
76
77 // Delete any existing list entries.
78 m_Labels.clear();
79
80 double xmin = DBL_MAX;
81 double xmax = -DBL_MAX;
82 double ymin = DBL_MAX;
83 double ymax = -DBL_MAX;
84 double zmin = DBL_MAX;
85 double zmax = -DBL_MAX;
86
87 m_DepthMin = DBL_MAX;
88 double depthmax = -DBL_MAX;
89
90 m_DateMin = INT_MAX;
91 int datemax = -1;
92 complete_dateinfo = true;
93
94 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
95 traverses[f].clear();
96 }
97 tubes.clear();
98
99 // Ultimately we probably want different types (subclasses perhaps?) for
100 // underground and surface data, so we don't need to store LRUD for surface
101 // stuff.
102 traverse * current_traverse = NULL;
103 vector<XSect> * current_tube = NULL;
104
105 map<wxString, LabelInfo *> labelmap;
106 list<LabelInfo*>::const_iterator last_mapped_label = m_Labels.begin();
107
108 int result;
109 img_point prev_pt = {0,0,0};
110 bool current_polyline_is_surface = false;
111 int current_flags = 0;
112 string current_label;
113 bool pending_move = false;
114 // When legs within a traverse have different surface/splay/duplicate
115 // flags, we split it into contiguous traverses of each flag combination,
116 // but we need to track these so we can assign the error statistics to all
117 // of them. So we keep counts of how many of each combination we've
118 // generated for the current traverse.
119 size_t n_traverses[8];
120 memset(n_traverses, 0, sizeof(n_traverses));
121 do {
122#if 0
123 if (++items % 200 == 0) {
124 long pos = ftell(survey->fh);
125 int progress = int((double(pos) / double(file_size)) * 100.0);
126 // SetProgress(progress);
127 }
128#endif
129
130 img_point pt;
131 result = img_read_item(survey, &pt);
132 switch (result) {
133 case img_MOVE:
134 memset(n_traverses, 0, sizeof(n_traverses));
135 pending_move = true;
136 prev_pt = pt;
137 break;
138
139 case img_LINE: {
140 // Update survey extents.
141 if (pt.x < xmin) xmin = pt.x;
142 if (pt.x > xmax) xmax = pt.x;
143 if (pt.y < ymin) ymin = pt.y;
144 if (pt.y > ymax) ymax = pt.y;
145 if (pt.z < zmin) zmin = pt.z;
146 if (pt.z > zmax) zmax = pt.z;
147
148 int date = survey->days1;
149 if (date != -1) {
150 date += (survey->days2 - date) / 2;
151 if (date < m_DateMin) m_DateMin = date;
152 if (date > datemax) datemax = date;
153 } else {
154 complete_dateinfo = false;
155 }
156
157 int flags = survey->flags &
158 (img_FLAG_SURFACE|img_FLAG_SPLAY|img_FLAG_DUPLICATE);
159 bool is_surface = (flags & img_FLAG_SURFACE);
160 bool is_splay = (flags & img_FLAG_SPLAY);
161 bool is_dupe = (flags & img_FLAG_DUPLICATE);
162
163 if (!is_surface) {
164 if (pt.z < m_DepthMin) m_DepthMin = pt.z;
165 if (pt.z > depthmax) depthmax = pt.z;
166 }
167 if (is_splay)
168 m_HasSplays = true;
169 if (is_dupe)
170 m_HasDupes = true;
171 if (pending_move ||
172 current_flags != flags ||
173 current_label != survey->label) {
174 if (!current_polyline_is_surface && current_traverse) {
175 //FixLRUD(*current_traverse);
176 }
177
178 ++n_traverses[flags];
179 // Start new traverse (surface or underground).
180 if (is_surface) {
181 m_HasSurfaceLegs = true;
182 } else {
183 m_HasUndergroundLegs = true;
184 // The previous point was at a surface->ug transition.
185 if (current_polyline_is_surface) {
186 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
187 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
188 }
189 }
190 traverses[flags].push_back(traverse(survey->label));
191 current_traverse = &traverses[flags].back();
192 current_traverse->flags = survey->flags;
193
194 current_polyline_is_surface = is_surface;
195 current_flags = flags;
196 current_label = survey->label;
197
198 if (pending_move) {
199 // Update survey extents. We only need to do this if
200 // there's a pending move, since for a surface <->
201 // underground transition, we'll already have handled
202 // this point.
203 if (prev_pt.x < xmin) xmin = prev_pt.x;
204 if (prev_pt.x > xmax) xmax = prev_pt.x;
205 if (prev_pt.y < ymin) ymin = prev_pt.y;
206 if (prev_pt.y > ymax) ymax = prev_pt.y;
207 if (prev_pt.z < zmin) zmin = prev_pt.z;
208 if (prev_pt.z > zmax) zmax = prev_pt.z;
209 }
210
211 current_traverse->push_back(PointInfo(prev_pt));
212 }
213
214 current_traverse->push_back(PointInfo(pt, date));
215
216 prev_pt = pt;
217 pending_move = false;
218 break;
219 }
220
221 case img_LABEL: {
222 wxString s(survey->label, wxConvUTF8);
223 if (s.empty()) {
224 // If label isn't valid UTF-8 then this conversion will
225 // give an empty string. In this case, assume that the
226 // label is CP1252 (the Microsoft superset of ISO8859-1).
227 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
228 s = wxString(survey->label, ConvCP1252);
229 if (s.empty()) {
230 // Or if that doesn't work (ConvCP1252 doesn't like
231 // strings with some bytes in) let's just go for
232 // ISO8859-1.
233 s = wxString(survey->label, wxConvISO8859_1);
234 }
235 }
236 int flags = img2aven(survey->flags);
237 LabelInfo* label = new LabelInfo(pt, s, flags);
238 if (label->IsEntrance()) {
239 m_NumEntrances++;
240 }
241 if (label->IsFixedPt()) {
242 m_NumFixedPts++;
243 }
244 if (label->IsExportedPt()) {
245 m_NumExportedPts++;
246 }
247 m_Labels.push_back(label);
248 break;
249 }
250
251 case img_XSECT: {
252 if (!current_tube) {
253 // Start new current_tube.
254 tubes.push_back(vector<XSect>());
255 current_tube = &tubes.back();
256 }
257
258 LabelInfo * lab;
259 wxString label(survey->label, wxConvUTF8);
260 map<wxString, LabelInfo *>::const_iterator p;
261 p = labelmap.find(label);
262 if (p != labelmap.end()) {
263 lab = p->second;
264 } else {
265 // Initialise labelmap lazily - we may have no
266 // cross-sections.
267 list<LabelInfo*>::const_iterator i;
268 if (labelmap.empty()) {
269 i = m_Labels.begin();
270 } else {
271 i = last_mapped_label;
272 ++i;
273 }
274 while (i != m_Labels.end() && (*i)->GetText() != label) {
275 labelmap[(*i)->GetText()] = *i;
276 ++i;
277 }
278 last_mapped_label = i;
279 if (i == m_Labels.end()) {
280 // Unattached cross-section - ignore for now.
281 printf("unattached cross-section\n");
282 if (current_tube->size() <= 1)
283 tubes.resize(tubes.size() - 1);
284 current_tube = NULL;
285 if (!m_Labels.empty())
286 --last_mapped_label;
287 break;
288 }
289 lab = *i;
290 labelmap[label] = lab;
291 }
292
293 int date = survey->days1;
294 if (date != -1) {
295 date += (survey->days2 - date) / 2;
296 if (date < m_DateMin) m_DateMin = date;
297 if (date > datemax) datemax = date;
298 }
299
300 current_tube->emplace_back(lab, date, survey->l, survey->r, survey->u, survey->d);
301 break;
302 }
303
304 case img_XSECT_END:
305 // Finish off current_tube.
306 // If there's only one cross-section in the tube, just
307 // discard it for now. FIXME: we should handle this
308 // when we come to skinning the tubes.
309 if (current_tube && current_tube->size() <= 1)
310 tubes.resize(tubes.size() - 1);
311 current_tube = NULL;
312 break;
313
314 case img_ERROR_INFO: {
315 if (survey->E == 0.0) {
316 // Currently cavern doesn't spot all articulating traverses
317 // so we assume that any traverse with no error isn't part
318 // of a loop. FIXME: fix cavern!
319 break;
320 }
321 m_HasErrorInformation = true;
322 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
323 list<traverse>::reverse_iterator t = traverses[f].rbegin();
324 size_t n = n_traverses[f];
325 n_traverses[f] = 0;
326 while (n) {
327 assert(t != traverses[f].rend());
328 t->n_legs = survey->n_legs;
329 t->length = survey->length;
330 t->errors[traverse::ERROR_3D] = survey->E;
331 t->errors[traverse::ERROR_H] = survey->H;
332 t->errors[traverse::ERROR_V] = survey->V;
333 --n;
334 ++t;
335 }
336 }
337 break;
338 }
339
340 case img_BAD: {
341 m_Labels.clear();
342
343 // FIXME: Do we need to reset all these? - Olly
344 m_NumFixedPts = 0;
345 m_NumExportedPts = 0;
346 m_NumEntrances = 0;
347 m_HasUndergroundLegs = false;
348 m_HasSplays = false;
349 m_HasSurfaceLegs = false;
350
351 img_close(survey);
352
353 return img_error2msg(img_error());
354 }
355
356 default:
357 break;
358 }
359 } while (result != img_STOP);
360
361 if (!current_polyline_is_surface && current_traverse) {
362 //FixLRUD(*current_traverse);
363 }
364
365 // Finish off current_tube.
366 // If there's only one cross-section in the tube, just
367 // discard it for now. FIXME: we should handle this
368 // when we come to skinning the tubes.
369 if (current_tube && current_tube->size() <= 1)
370 tubes.resize(tubes.size() - 1);
371
372 m_separator = survey->separator;
373 m_Title = wxString(survey->title, wxConvUTF8);
374 m_DateStamp_numeric = survey->datestamp_numeric;
375 if (survey->cs) {
376 m_cs_proj = wxString(survey->cs, wxConvUTF8);
377 } else {
378 m_cs_proj = wxString();
379 }
380 if (strcmp(survey->datestamp, "?") == 0) {
381 /* TRANSLATORS: used a processed survey with no processing date/time info */
382 m_DateStamp = wmsg(/*Date and time not available.*/108);
383 } else if (survey->datestamp[0] == '@') {
384 const struct tm * tm = localtime(&m_DateStamp_numeric);
385 char buf[256];
386 /* TRANSLATORS: This is the date format string used to timestamp .3d
387 * files internally. Probably best to keep it the same for all
388 * translations. */
389 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
390 m_DateStamp = wxString(buf, wxConvUTF8);
391 }
392 if (m_DateStamp.empty()) {
393 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
394 }
395 img_close(survey);
396
397 // Check we've actually loaded some legs or stations!
398 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
399 return (/*No survey data in 3d file “%s”*/202);
400 }
401
402 if (traverses[0].empty() &&
403 traverses[1].empty() &&
404 traverses[2].empty() &&
405 traverses[3].empty() &&
406 traverses[4].empty() &&
407 traverses[5].empty() &&
408 traverses[6].empty() &&
409 traverses[7].empty()) {
410 // No legs, so get survey extents from stations
411 list<LabelInfo*>::const_iterator i;
412 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
413 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
414 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
415 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
416 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
417 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
418 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
419 }
420 }
421
422 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
423
424 if (datemax < m_DateMin) m_DateMin = datemax;
425 m_DateExt = datemax - m_DateMin;
426
427 // Centre the dataset around the origin.
428 CentreDataset(Vector3(xmin, ymin, zmin));
429
430 if (depthmax < m_DepthMin) {
431 m_DepthMin = 0;
432 m_DepthExt = 0;
433 } else {
434 m_DepthExt = depthmax - m_DepthMin;
435 m_DepthMin -= GetOffset().GetZ();
436 }
437
438#if 0
439 printf("time to load = %.3f\n", (double)timer.Time());
440#endif
441
442 return 0; // OK
443}
444
445void Model::CentreDataset(const Vector3& vmin)
446{
447 // Centre the dataset around the origin.
448
449 m_Offset = vmin + (m_Ext * 0.5);
450
451 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
452 list<traverse>::iterator t = traverses[f].begin();
453 while (t != traverses[f].end()) {
454 assert(t->size() > 1);
455 vector<PointInfo>::iterator pos = t->begin();
456 while (pos != t->end()) {
457 Point & point = *pos++;
458 point -= m_Offset;
459 }
460 ++t;
461 }
462 }
463
464 list<LabelInfo*>::iterator lpos = m_Labels.begin();
465 while (lpos != m_Labels.end()) {
466 Point & point = **lpos++;
467 point -= m_Offset;
468 }
469}
470
471void
472Model::do_prepare_tubes() const
473{
474 // Fill in "right_bearing" for each cross-section.
475 for (auto&& tube : tubes) {
476 assert(tube.size() > 1);
477 Vector3 U[4];
478 XSect* prev_pt_v = NULL;
479 Vector3 last_right(1.0, 0.0, 0.0);
480
481 vector<XSect>::iterator i = tube.begin();
482 vector<XSect>::size_type segment = 0;
483 while (i != tube.end()) {
484 // get the coordinates of this vertex
485 XSect & pt_v = *i++;
486
487 bool cover_end = false;
488
489 Vector3 right, up;
490
491 const Vector3 up_v(0.0, 0.0, 1.0);
492
493 if (segment == 0) {
494 assert(i != tube.end());
495 // first segment
496
497 // get the coordinates of the next vertex
498 const XSect & next_pt_v = *i;
499
500 // calculate vector from this pt to the next one
501 Vector3 leg_v = next_pt_v - pt_v;
502
503 // obtain a vector in the LRUD plane
504 right = leg_v * up_v;
505 if (right.magnitude() == 0) {
506 right = last_right;
507 // Obtain a second vector in the LRUD plane,
508 // perpendicular to the first.
509 //up = right * leg_v;
510 up = up_v;
511 } else {
512 last_right = right;
513 up = up_v;
514 }
515
516 cover_end = true;
517 } else if (segment + 1 == tube.size()) {
518 // last segment
519
520 // Calculate vector from the previous pt to this one.
521 Vector3 leg_v = pt_v - *prev_pt_v;
522
523 // Obtain a horizontal vector in the LRUD plane.
524 right = leg_v * up_v;
525 if (right.magnitude() == 0) {
526 right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
527 // Obtain a second vector in the LRUD plane,
528 // perpendicular to the first.
529 //up = right * leg_v;
530 up = up_v;
531 } else {
532 last_right = right;
533 up = up_v;
534 }
535
536 cover_end = true;
537 } else {
538 assert(i != tube.end());
539 // Intermediate segment.
540
541 // Get the coordinates of the next vertex.
542 const XSect & next_pt_v = *i;
543
544 // Calculate vectors from this vertex to the
545 // next vertex, and from the previous vertex to
546 // this one.
547 Vector3 leg1_v = pt_v - *prev_pt_v;
548 Vector3 leg2_v = next_pt_v - pt_v;
549
550 // Obtain horizontal vectors perpendicular to
551 // both legs, then normalise and average to get
552 // a horizontal bisector.
553 Vector3 r1 = leg1_v * up_v;
554 Vector3 r2 = leg2_v * up_v;
555 r1.normalise();
556 r2.normalise();
557 right = r1 + r2;
558 if (right.magnitude() == 0) {
559 // This is the "mid-pitch" case...
560 right = last_right;
561 }
562 if (r1.magnitude() == 0) {
563 up = up_v;
564
565 // Rotate pitch section to minimise the
566 // "torsional stress" - FIXME: use
567 // triangles instead of rectangles?
568 int shift = 0;
569 double maxdotp = 0;
570
571 // Scale to unit vectors in the LRUD plane.
572 right.normalise();
573 up.normalise();
574 Vector3 vec = up - right;
575 for (int orient = 0; orient <= 3; ++orient) {
576 Vector3 tmp = U[orient] - prev_pt_v->GetPoint();
577 tmp.normalise();
578 double dotp = dot(vec, tmp);
579 if (dotp > maxdotp) {
580 maxdotp = dotp;
581 shift = orient;
582 }
583 }
584 if (shift) {
585 if (shift != 2) {
586 Vector3 temp(U[0]);
587 U[0] = U[shift];
588 U[shift] = U[2];
589 U[2] = U[shift ^ 2];
590 U[shift ^ 2] = temp;
591 } else {
592 swap(U[0], U[2]);
593 swap(U[1], U[3]);
594 }
595 }
596#if 0
597 // Check that the above code actually permuted
598 // the vertices correctly.
599 shift = 0;
600 maxdotp = 0;
601 for (int j = 0; j <= 3; ++j) {
602 Vector3 tmp = U[j] - *prev_pt_v;
603 tmp.normalise();
604 double dotp = dot(vec, tmp);
605 if (dotp > maxdotp) {
606 maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
607 shift = j;
608 }
609 }
610 if (shift) {
611 printf("New shift = %d!\n", shift);
612 shift = 0;
613 maxdotp = 0;
614 for (int j = 0; j <= 3; ++j) {
615 Vector3 tmp = U[j] - *prev_pt_v;
616 tmp.normalise();
617 double dotp = dot(vec, tmp);
618 printf(" %d : %.8f\n", j, dotp);
619 }
620 }
621#endif
622 } else {
623 up = up_v;
624 }
625 last_right = right;
626 }
627
628 // Scale to unit vectors in the LRUD plane.
629 right.normalise();
630 up.normalise();
631
632 double l = fabs(pt_v.GetL());
633 double r = fabs(pt_v.GetR());
634 double u = fabs(pt_v.GetU());
635 double d = fabs(pt_v.GetD());
636
637 // Produce coordinates of the corners of the LRUD "plane".
638 Vector3 v[4];
639 v[0] = pt_v.GetPoint() - right * l + up * u;
640 v[1] = pt_v.GetPoint() + right * r + up * u;
641 v[2] = pt_v.GetPoint() + right * r - up * d;
642 v[3] = pt_v.GetPoint() - right * l - up * d;
643
644 prev_pt_v = &pt_v;
645 U[0] = v[0];
646 U[1] = v[1];
647 U[2] = v[2];
648 U[3] = v[3];
649
650 // FIXME: Store rather than recomputing on each draw?
651 (void)cover_end;
652
653 pt_v.set_right_bearing(deg(atan2(right.GetX(), right.GetY())));
654
655 ++segment;
656 }
657 }
658}
659
660void
661SurveyFilter::add(const wxString& name)
662{
663 auto it = filters.lower_bound(name);
664 if (it != filters.end()) {
665 // It's invalid to add a survey which is already present.
666 assert(*it != name);
667 // Check if a survey prefixing name is visible.
668 if (name.StartsWith(*it) && name[it->size()] == separator) {
669 redundant_filters.insert(name);
670 return;
671 }
672 }
673 while (it != filters.begin()) {
674 --it;
675 const wxString& s = *it;
676 if (s.size() <= name.size()) break;
677 if (s.StartsWith(name) && s[name.size()] == separator) {
678 redundant_filters.insert(s);
679 it = filters.erase(it);
680 }
681 }
682 filters.insert(name);
683}
684
685void
686SurveyFilter::remove(const wxString& name)
687{
688 if (filters.erase(name) == 0) {
689 redundant_filters.erase(name);
690 return;
691 }
692 if (redundant_filters.empty()) {
693 return;
694 }
695 auto it = redundant_filters.upper_bound(name);
696 while (it != redundant_filters.begin()) {
697 --it;
698 // Check if a survey prefixed by name should be made visible.
699 const wxString& s = *it;
700 if (s.size() <= name.size()) {
701 break;
702 }
703 if (!(s.StartsWith(name) && s[name.size()] == separator))
704 break;
705 filters.insert(s);
706 it = redundant_filters.erase(it);
707 }
708}
709
710void
711SurveyFilter::SetSeparator(wxChar separator_)
712{
713 if (separator_ == separator) return;
714
715 separator = separator_;
716
717 if (filters.empty()) {
718 return;
719 }
720
721 // Move aside all the filters already set and re-add() them so they get
722 // split into redundant_filters appropriately.
723 std::set<wxString, std::greater<wxString>> old_filters;
724 std::set<wxString, std::greater<wxString>> old_redundant_filters;
725 swap(filters, old_filters);
726 swap(redundant_filters, old_redundant_filters);
727 for (auto& s : filters) {
728 add(s);
729 }
730 for (auto& s : redundant_filters) {
731 add(s);
732 }
733}
734
735bool
736SurveyFilter::CheckVisible(const wxString& name) const
737{
738 auto it = filters.lower_bound(name);
739 if (it == filters.end()) {
740 // There's no filter <= name so name is excluded.
741 return false;
742 }
743 if (*it == name) {
744 // Exact match.
745 return true;
746 }
747 // Check if a survey prefixing name is visible.
748 if (name.StartsWith(*it) && name[it->size()] == separator)
749 return true;
750 return false;
751}
Note: See TracBrowser for help on using the repository browser.