source: git/src/aventreectrl.cc @ 04078a7

faster-cavernloglog-selectwalls-datawalls-data-hanging-as-warning
Last change on this file since 04078a7 was ed7f3fc, checked in by Olly Betts <olly@…>, 17 months ago

Accept weird .3d files from therion

Therion can create .3d files which have empty components in the
station name. That's not really valid by Survex's definition of
station names, but it's not very helpful to reject such files, so
just disable the checks that were rejecting this.

The result is empty entries in the survey tree, but I don't see
what we can really do that's better than that.

Reported by Vasily Vl. Suhachev.

  • Property mode set to 100644
File size: 15.8 KB
Line 
1//
2//  aventreectrl.cc
3//
4//  Tree control used for the survey tree.
5//
6//  Copyright (C) 2001, Mark R. Shinwell.
7//  Copyright (C) 2001-2003,2005,2006,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 "aventreectrl.h"
30#include "mainfrm.h"
31
32#include <stack>
33
34using namespace std;
35
36// STATE_BLANK is used for stations which are siblings of surveys which have
37// select checkboxes.
38enum { STATE_BLANK = 0, STATE_OFF, STATE_ON };
39
40/* XPM */
41static const char *blank_xpm[] = {
42/* columns rows colors chars-per-pixel */
43"15 15 1 1",
44"  c None",
45/* pixels */
46"               ",
47"               ",
48"               ",
49"               ",
50"               ",
51"               ",
52"               ",
53"               ",
54"               ",
55"               ",
56"               ",
57"               ",
58"               ",
59"               ",
60"               "
61};
62
63/* XPM */
64static const char *off_xpm[] = {
65/* columns rows colors chars-per-pixel */
66"15 15 2 1",
67". c #000000",
68"  c None",
69/* pixels */
70"               ",
71"               ",
72" ............  ",
73" .          .  ",
74" .          .  ",
75" .          .  ",
76" .          .  ",
77" .          .  ",
78" .          .  ",
79" .          .  ",
80" .          .  ",
81" .          .  ",
82" .          .  ",
83" ............  ",
84"               "
85};
86
87/* XPM */
88static const char *on_xpm[] = {
89/* columns rows colors chars-per-pixel */
90"15 15 3 1",
91". c #000000",
92"X c #007F28",
93"  c None",
94/* pixels */
95"               ",
96"               ",
97" ............XX",
98" .          XXX",
99" .         XXXX",
100" .        XXXX ",
101" .       XXXX  ",
102" .      XXXX.  ",
103" . XX  XXXX .  ",
104" . XXXXXXX  .  ",
105" .  XXXXX   .  ",
106" .   XXX    .  ",
107" .    X     .  ",
108" ............  ",
109"               "
110};
111
112BEGIN_EVENT_TABLE(AvenTreeCtrl, wxTreeCtrl)
113    EVT_MOTION(AvenTreeCtrl::OnMouseMove)
114    EVT_LEAVE_WINDOW(AvenTreeCtrl::OnLeaveWindow)
115    EVT_TREE_SEL_CHANGED(-1, AvenTreeCtrl::OnSelChanged)
116    EVT_TREE_ITEM_ACTIVATED(-1, AvenTreeCtrl::OnItemActivated)
117    EVT_CHAR(AvenTreeCtrl::OnKeyPress)
118    EVT_TREE_ITEM_MENU(-1, AvenTreeCtrl::OnMenu)
119    EVT_MENU(menu_SURVEY_SHOW_ALL, AvenTreeCtrl::OnRestrict)
120    EVT_MENU(menu_SURVEY_RESTRICT, AvenTreeCtrl::OnRestrict)
121    EVT_MENU(menu_SURVEY_HIDE, AvenTreeCtrl::OnHide)
122    EVT_MENU(menu_SURVEY_SHOW, AvenTreeCtrl::OnShow)
123    EVT_MENU(menu_SURVEY_HIDE_SIBLINGS, AvenTreeCtrl::OnHideSiblings)
124    EVT_TREE_STATE_IMAGE_CLICK(-1, AvenTreeCtrl::OnStateClick)
125END_EVENT_TABLE()
126
127AvenTreeCtrl::AvenTreeCtrl(MainFrm* parent, wxWindow* window_parent) :
128    wxTreeCtrl(window_parent, -1),
129    m_Parent(parent),
130    m_Enabled(false),
131    m_LastItem(),
132    m_BackgroundColour(),
133    m_SelValid(false),
134    menu_data(NULL)
135{
136    wxImageList* img_list = new wxImageList(15, 15, 2);
137    img_list->Add(wxBitmap(blank_xpm));
138    img_list->Add(wxBitmap(off_xpm));
139    img_list->Add(wxBitmap(on_xpm));
140    AssignStateImageList(img_list);
141}
142
143void AvenTreeCtrl::FillTree(const wxString& root_name)
144{
145    Freeze();
146    m_Enabled = false;
147    m_LastItem = wxTreeItemId();
148    m_SelValid = false;
149    DeleteAllItems();
150
151    const wxChar separator = m_Parent->GetSeparator();
152    filter.clear();
153    filter.SetSeparator(separator);
154
155    // Create the root of the tree.
156    wxTreeItemId treeroot = AddRoot(root_name);
157
158    // Fill the tree of stations and prefixes.
159    stack<wxTreeItemId> previous_ids;
160    wxString current_prefix;
161    wxTreeItemId current_id = treeroot;
162
163    list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
164    while (pos != m_Parent->GetLabelsEnd()) {
165        LabelInfo* label = *pos++;
166
167        if (label->IsAnon()) continue;
168
169        // Determine the current prefix.
170        wxString prefix = label->GetText().BeforeLast(separator);
171
172        // Determine if we're still on the same prefix.
173        if (prefix == current_prefix) {
174            // no need to fiddle with branches...
175        }
176        // If not, then see if we've descended to a new prefix.
177        else if (prefix.length() > current_prefix.length() &&
178                 prefix.StartsWith(current_prefix) &&
179                 (prefix[current_prefix.length()] == separator ||
180                  current_prefix.empty())) {
181            // We have, so start as many new branches as required.
182            int current_prefix_length = current_prefix.length();
183            current_prefix = prefix;
184            size_t next_dot = current_prefix_length;
185            if (!next_dot) --next_dot;
186            do {
187                size_t prev_dot = next_dot + 1;
188
189                // Extract the next bit of prefix.
190                next_dot = prefix.find(separator, prev_dot + 1);
191
192                wxString bit = prefix.substr(prev_dot, next_dot - prev_dot);
193                // Sigh, therion can produce files with empty components in
194                // station names!
195                // assert(!bit.empty());
196
197                // Add the current tree ID to the stack.
198                previous_ids.push(current_id);
199
200                // Append the new item to the tree and set this as the current branch.
201                current_id = AppendItem(current_id, bit);
202                SetItemData(current_id, new TreeData(prefix.substr(0, next_dot)));
203            } while (next_dot != wxString::npos);
204        }
205        // Otherwise, we must have moved up, and possibly then down again.
206        else {
207            size_t count = 0;
208            bool ascent_only = (prefix.length() < current_prefix.length() &&
209                                current_prefix.StartsWith(prefix) &&
210                                (current_prefix[prefix.length()] == separator ||
211                                 prefix.empty()));
212            if (!ascent_only) {
213                // Find out how much of the current prefix and the new prefix
214                // are the same.
215                // Note that we require a match of a whole number of parts
216                // between dots!
217                size_t n = min(prefix.length(), current_prefix.length());
218                size_t i;
219                for (i = 0; i < n && prefix[i] == current_prefix[i]; ++i) {
220                    if (prefix[i] == separator) count = i + 1;
221                }
222            } else {
223                count = prefix.length() + 1;
224            }
225
226            // Extract the part of the current prefix after the bit (if any)
227            // which has matched.
228            // This gives the prefixes to ascend over.
229            wxString prefixes_ascended = current_prefix.substr(count);
230
231            // Count the number of prefixes to ascend over.
232            int num_prefixes = prefixes_ascended.Freq(separator);
233
234            // Reverse up over these prefixes.
235            for (int i = 1; i <= num_prefixes; i++) {
236                previous_ids.pop();
237            }
238            current_id = previous_ids.top();
239            previous_ids.pop();
240
241            if (!ascent_only) {
242                // Add branches for this new part.
243                size_t next_dot = count - 1;
244                do {
245                    size_t prev_dot = next_dot + 1;
246
247                    // Extract the next bit of prefix.
248                    next_dot = prefix.find(separator, prev_dot + 1);
249
250                    wxString bit = prefix.substr(prev_dot, next_dot - prev_dot);
251                    // Sigh, therion can produce files with empty components in
252                    // station names!
253                    // assert(!bit.empty());
254
255                    // Add the current tree ID to the stack.
256                    previous_ids.push(current_id);
257
258                    // Append the new item to the tree and set this as the current branch.
259                    current_id = AppendItem(current_id, bit);
260                    SetItemData(current_id, new TreeData(prefix.substr(0, next_dot)));
261                } while (next_dot != wxString::npos);
262            }
263
264            current_prefix = prefix;
265        }
266
267        // Now add the leaf.
268        wxString bit = label->GetText().AfterLast(separator);
269        // Sigh, therion can produce files with empty components in station
270        // names!
271        // assert(!bit.empty());
272        wxTreeItemId id = AppendItem(current_id, bit);
273        SetItemData(id, new TreeData(label));
274        label->tree_id = id;
275        // Set the colour for an item in the survey tree.
276        if (label->IsEntrance()) {
277            // Entrances are green (like entrance blobs).
278            SetItemTextColour(id, wxColour(0, 255, 40));
279        } else if (label->IsSurface()) {
280            // Surface stations are dark green.
281            SetItemTextColour(id, wxColour(49, 158, 79));
282        }
283    }
284
285    Expand(treeroot);
286    m_Enabled = true;
287    Thaw();
288}
289
290constexpr auto TREE_MASK = wxTREE_HITTEST_ONITEMLABEL |
291                           wxTREE_HITTEST_ONITEMRIGHT |
292                           wxTREE_HITTEST_ONITEMSTATEICON;
293
294void AvenTreeCtrl::OnMouseMove(wxMouseEvent& event)
295{
296    if (!m_Enabled || m_Parent->Animating())
297        return;
298
299    int flags;
300    wxTreeItemId pos = HitTest(event.GetPosition(), flags);
301    if (!(flags & TREE_MASK)) {
302        pos = wxTreeItemId();
303    }
304    if (pos == m_LastItem) return;
305    if (pos.IsOk()) {
306        const TreeData* data = static_cast<const TreeData*>(GetItemData(pos));
307        m_Parent->DisplayTreeInfo(data);
308        if (data && !data->IsStation()) {
309            // For stations, MainFrm calls back to SetHere(), but for surveys
310            // we need to do that ourselves.
311            SetHere(pos);
312        }
313    } else {
314        m_Parent->DisplayTreeInfo();
315    }
316}
317
318void AvenTreeCtrl::SetHere(wxTreeItemId pos)
319{
320    if (pos == m_LastItem) return;
321
322    if (m_LastItem.IsOk()) {
323        SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
324    }
325    if (pos.IsOk()) {
326        m_BackgroundColour = GetItemBackgroundColour(pos);
327        SetItemBackgroundColour(pos, wxColour(180, 180, 180));
328    }
329    m_LastItem = pos;
330}
331
332void AvenTreeCtrl::OnLeaveWindow(wxMouseEvent&)
333{
334    if (m_LastItem.IsOk()) {
335        SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
336        m_LastItem = wxTreeItemId();
337    }
338    m_Parent->DisplayTreeInfo();
339}
340
341void AvenTreeCtrl::OnSelChanged(wxTreeEvent&)
342{
343    m_SelValid = true;
344}
345
346void AvenTreeCtrl::OnItemActivated(wxTreeEvent& e)
347{
348    if (!m_Enabled) return;
349
350    m_Parent->TreeItemSelected(GetItemData(e.GetItem()));
351}
352
353void AvenTreeCtrl::OnMenu(wxTreeEvent& e)
354{
355    if (!m_Enabled) return;
356
357    const TreeData* data = static_cast<const TreeData*>(GetItemData(e.GetItem()));
358    menu_data = data;
359    menu_item = e.GetItem();
360    if (!data) {
361        // Root:
362        wxMenu menu;
363        /* TRANSLATORS: In aven's survey tree, right-clicking on the root
364         * gives a pop-up menu and this is an option (but only enabled if
365         * the view is restricted to a subsurvey). It reloads the current
366         * survey file with the who survey visible.
367         */
368        menu.Append(menu_SURVEY_SHOW_ALL, wmsg(/*Show all*/245));
369        if (m_Parent->GetSurvey().empty())
370            menu.Enable(menu_SURVEY_SHOW_ALL, false);
371        PopupMenu(&menu);
372    } else if (data->GetLabel()) {
373        // Station: name is data->GetLabel()->GetText()
374    } else {
375        // Survey:
376        wxMenu menu;
377        /* TRANSLATORS: In aven's survey tree, right-clicking on a survey
378         * name gives a pop-up menu and this is an option.  It reloads the
379         * current survey file with the view restricted to the survey
380         * clicked upon.
381         */
382        menu.Append(menu_SURVEY_RESTRICT, wmsg(/*Hide others*/246));
383        menu.AppendSeparator();
384        //menu.Append(menu_SURVEY_HIDE, wmsg(/*&Hide*/407));
385        menu.Append(menu_SURVEY_SHOW, wmsg(/*&Show*/409));
386        //menu.Append(menu_SURVEY_HIDE_SIBLINGS, wmsg(/*Hide si&blings*/388));
387        switch (GetItemState(menu_item)) {
388            case STATE_ON: // Currently shown.
389                menu.Enable(menu_SURVEY_SHOW, false);
390                break;
391#if 0
392            case STATE_HIDDEN: // Currently hidden.
393                menu.Enable(menu_SURVEY_RESTRICT, false);
394                menu.Enable(menu_SURVEY_HIDE, false);
395                menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
396                break;
397            case STATE_OFF:
398                menu.Enable(menu_SURVEY_HIDE, false);
399                menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
400                break;
401#endif
402        }
403        PopupMenu(&menu);
404    }
405    menu_data = NULL;
406    e.Skip();
407}
408
409bool AvenTreeCtrl::GetSelectionData(wxTreeItemData** data) const
410{
411    assert(m_Enabled);
412    assert(data);
413
414    if (!m_SelValid) {
415        return false;
416    }
417
418    wxTreeItemId id = GetSelection();
419    if (id.IsOk()) {
420        *data = GetItemData(id);
421    }
422
423    return id.IsOk() && *data;
424}
425
426void AvenTreeCtrl::UnselectAll()
427{
428    m_SelValid = false;
429    wxTreeCtrl::UnselectAll();
430}
431
432void AvenTreeCtrl::OnKeyPress(wxKeyEvent &e)
433{
434    switch (e.GetKeyCode()) {
435        case WXK_ESCAPE:
436            m_Parent->ClearTreeSelection();
437            break;
438        case WXK_RETURN: {
439            wxTreeItemId id = GetSelection();
440            if (id.IsOk()) {
441                if (ItemHasChildren(id)) {
442                    // If on a branch, expand/contract it.
443                    if (IsExpanded(id)) {
444                        Collapse(id);
445                    } else {
446                        Expand(id);
447                    }
448                } else {
449                    // If on a station, centre on it by selecting it twice.
450                    m_Parent->TreeItemSelected(GetItemData(id));
451                    m_Parent->TreeItemSelected(GetItemData(id));
452                }
453            }
454            break;
455        }
456        case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
457        case WXK_HOME: case WXK_END: case WXK_PAGEUP: case WXK_PAGEDOWN:
458            e.Skip();
459            break;
460        default:
461            // Pass key event to MainFrm which will pass to GfxCore which will
462            // pass to GUIControl.
463            m_Parent->OnKeyPress(e);
464            break;
465    }
466}
467
468void AvenTreeCtrl::OnRestrict(wxCommandEvent&)
469{
470    m_Parent->RestrictTo(menu_data ? menu_data->GetSurvey() : wxString());
471}
472
473void AvenTreeCtrl::OnHide(wxCommandEvent&)
474{
475    // Shouldn't be available for the root item.
476    wxASSERT(menu_data);
477    // Hide should be disabled unless the item is explicitly shown.
478    wxASSERT(GetItemState(menu_item) == STATE_ON);
479    SetItemState(menu_item, STATE_OFF);
480    filter.remove(menu_data->GetSurvey());
481#if 0
482    Freeze();
483    // Show siblings if not already shown or hidden.
484    wxTreeItemId i = menu_item;
485    while ((i = GetPrevSibling(i)).IsOk()) {
486        if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
487            SetItemState(i, 1);
488    }
489    i = menu_item;
490    while ((i = GetNextSibling(i)).IsOk()) {
491        if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
492            SetItemState(i, 1);
493    }
494    Thaw();
495#endif
496    m_Parent->ForceFullRedraw();
497}
498
499void AvenTreeCtrl::OnShow(wxCommandEvent&)
500{
501    // Shouldn't be available for the root item.
502    wxASSERT(menu_data);
503    auto old_state = GetItemState(menu_item);
504    // Show should be disabled for an explicitly shown item.
505    wxASSERT(old_state != STATE_ON);
506    Freeze();
507    SetItemState(menu_item, STATE_ON);
508    filter.add(menu_data->GetSurvey());
509    if (old_state == wxTREE_ITEMSTATE_NONE) {
510        // Hide siblings if not already shown or hidden.
511        wxTreeItemId i = menu_item;
512        while ((i = GetPrevSibling(i)).IsOk()) {
513            if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
514                const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
515                SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
516            }
517        }
518        i = menu_item;
519        while ((i = GetNextSibling(i)).IsOk()) {
520            if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
521                const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
522                SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
523            }
524        }
525    }
526    Thaw();
527    m_Parent->ForceFullRedraw();
528}
529
530void AvenTreeCtrl::OnHideSiblings(wxCommandEvent&)
531{
532    // Shouldn't be available for the root item.
533    wxASSERT(menu_data);
534    Freeze();
535    SetItemState(menu_item, STATE_ON);
536    filter.add(menu_data->GetSurvey());
537
538    wxTreeItemId i = menu_item;
539    while ((i = GetPrevSibling(i)).IsOk()) {
540        const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
541        filter.remove(data->GetSurvey());
542        SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
543    }
544    i = menu_item;
545    while ((i = GetNextSibling(i)).IsOk()) {
546        const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
547        filter.remove(data->GetSurvey());
548        SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
549    }
550    Thaw();
551    m_Parent->ForceFullRedraw();
552}
553
554void AvenTreeCtrl::OnStateClick(wxTreeEvent& e)
555{
556    auto item = e.GetItem();
557    const TreeData* data = static_cast<const TreeData*>(GetItemData(item));
558    switch (GetItemState(item)) {
559        case STATE_BLANK:
560            // Click on blank state icon for a station - let the tree handle
561            // this in the same way as a click on the label.
562            return;
563        case STATE_ON:
564            filter.remove(data->GetSurvey());
565            SetItemState(item, STATE_OFF);
566            break;
567        case STATE_OFF:
568            filter.add(data->GetSurvey());
569            SetItemState(item, STATE_ON);
570            break;
571    }
572    e.Skip();
573    m_Parent->ForceFullRedraw();
574}
Note: See TracBrowser for help on using the repository browser.