source: git/src/model.cc @ e588175

debug-cidebug-ci-sanitiserswalls-data
Last change on this file since e588175 was 1330b4d, checked in by Olly Betts <olly@…>, 3 months ago

Fix survey filtering with non-default separator

This fixes highlighting surveys you mouse-over in the survey pane
when viewing a Compass PLT file, and probably other things involving
survey filtering.

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