source: git/src/aventreectrl.cc @ 6969b17

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

Use wxID_ANY instead of literal -1

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