source: git/src/aventreectrl.cc @ 6e1a54e

RELEASE/1.2debug-cidebug-ci-sanitisersfaster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 6e1a54e was 9e8e1d6, checked in by Olly Betts <olly@…>, 6 years ago

Include survey tree status icon in mouseover area

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