source: git/src/aventreectrl.cc @ 25c7ef4

warn-only-for-hanging-survey
Last change on this file since 25c7ef4 was 51eab3b, checked in by Olly Betts <olly@…>, 3 months ago

aven: Support overlaying geodata

GDAL is now a required dependency.

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