source: git/src/model.cc @ 64fba42

Last change on this file since 64fba42 was 64fba42, checked in by Olly Betts <olly@…>, 11 months ago

Fix compiler warning

  • 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 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->E = survey->E;
331                        t->H = survey->H;
332                        t->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.GetY(), right.GetX())));
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.