source: git/src/aventreectrl.cc @ 76cf7f1

RELEASE/1.2debug-cidebug-ci-sanitiserswalls-data
Last change on this file since 76cf7f1 was 0642381, checked in by Olly Betts <olly@…>, 6 years ago

Highlight surveys on survey tree mouseover

This is cleaner than running a search to highlight them, and means
we can restore "double-click survey in tree to zoom".

  • Property mode set to 100644
File size: 15.5 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                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                    assert(!bit.empty());
250
251                    // Add the current tree ID to the stack.
252                    previous_ids.push(current_id);
253
254                    // Append the new item to the tree and set this as the current branch.
255                    current_id = AppendItem(current_id, bit);
256                    SetItemData(current_id, new TreeData(prefix.substr(0, next_dot)));
257                } while (next_dot != wxString::npos);
258            }
259
260            current_prefix = prefix;
261        }
262
263        // Now add the leaf.
264        wxString bit = label->GetText().AfterLast(separator);
265        assert(!bit.empty());
266        wxTreeItemId id = AppendItem(current_id, bit);
267        SetItemData(id, new TreeData(label));
268        label->tree_id = id;
269        // Set the colour for an item in the survey tree.
270        if (label->IsEntrance()) {
271            // Entrances are green (like entrance blobs).
272            SetItemTextColour(id, wxColour(0, 255, 40));
273        } else if (label->IsSurface()) {
274            // Surface stations are dark green.
275            SetItemTextColour(id, wxColour(49, 158, 79));
276        }
277    }
278
279    Expand(treeroot);
280    m_Enabled = true;
281    Thaw();
282}
283
284constexpr auto TREE_MASK = wxTREE_HITTEST_ONITEMLABEL |
285                           wxTREE_HITTEST_ONITEMRIGHT |
286                           wxTREE_HITTEST_ONITEMSTATEICON;
287
288void AvenTreeCtrl::OnMouseMove(wxMouseEvent& event)
289{
290    if (!m_Enabled || m_Parent->Animating())
291        return;
292
293    int flags;
294    wxTreeItemId pos = HitTest(event.GetPosition(), flags);
295    if (!(flags & TREE_MASK)) {
296        pos = wxTreeItemId();
297    }
298    if (pos == m_LastItem) return;
299    if (pos.IsOk()) {
300        const TreeData* data = static_cast<const TreeData*>(GetItemData(pos));
301        m_Parent->DisplayTreeInfo(data);
302        if (data && !data->IsStation()) {
303            // For stations, MainFrm calls back to SetHere(), but for surveys
304            // we need to do that ourselves.
305            SetHere(pos);
306        }
307    } else {
308        m_Parent->DisplayTreeInfo();
309    }
310}
311
312void AvenTreeCtrl::SetHere(wxTreeItemId pos)
313{
314    if (pos == m_LastItem) return;
315
316    if (m_LastItem.IsOk()) {
317        SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
318    }
319    if (pos.IsOk()) {
320        m_BackgroundColour = GetItemBackgroundColour(pos);
321        SetItemBackgroundColour(pos, wxColour(180, 180, 180));
322    }
323    m_LastItem = pos;
324}
325
326void AvenTreeCtrl::OnLeaveWindow(wxMouseEvent&)
327{
328    if (m_LastItem.IsOk()) {
329        SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
330        m_LastItem = wxTreeItemId();
331    }
332    m_Parent->DisplayTreeInfo();
333}
334
335void AvenTreeCtrl::OnSelChanged(wxTreeEvent&)
336{
337    m_SelValid = true;
338}
339
340void AvenTreeCtrl::OnItemActivated(wxTreeEvent& e)
341{
342    if (!m_Enabled) return;
343
344    m_Parent->TreeItemSelected(GetItemData(e.GetItem()));
345}
346
347void AvenTreeCtrl::OnMenu(wxTreeEvent& e)
348{
349    if (!m_Enabled) return;
350
351    const TreeData* data = static_cast<const TreeData*>(GetItemData(e.GetItem()));
352    menu_data = data;
353    menu_item = e.GetItem();
354    if (!data) {
355        // Root:
356        wxMenu menu;
357        /* TRANSLATORS: In aven's survey tree, right-clicking on the root
358         * gives a pop-up menu and this is an option (but only enabled if
359         * the view is restricted to a subsurvey). It reloads the current
360         * survey file with the who survey visible.
361         */
362        menu.Append(menu_SURVEY_SHOW_ALL, wmsg(/*Show all*/245));
363        if (m_Parent->GetSurvey().empty())
364            menu.Enable(menu_SURVEY_SHOW_ALL, false);
365        PopupMenu(&menu);
366    } else if (data->GetLabel()) {
367        // Station: name is data->GetLabel()->GetText()
368    } else {
369        // Survey:
370        wxMenu menu;
371        /* TRANSLATORS: In aven's survey tree, right-clicking on a survey
372         * name gives a pop-up menu and this is an option.  It reloads the
373         * current survey file with the view restricted to the survey
374         * clicked upon.
375         */
376        menu.Append(menu_SURVEY_RESTRICT, wmsg(/*Hide others*/246));
377        menu.AppendSeparator();
378        //menu.Append(menu_SURVEY_HIDE, wmsg(/*&Hide*/407));
379        menu.Append(menu_SURVEY_SHOW, wmsg(/*&Show*/409));
380        //menu.Append(menu_SURVEY_HIDE_SIBLINGS, wmsg(/*Hide si&blings*/388));
381        switch (GetItemState(menu_item)) {
382            case STATE_ON: // Currently shown.
383                menu.Enable(menu_SURVEY_SHOW, false);
384                break;
385#if 0
386            case STATE_HIDDEN: // Currently hidden.
387                menu.Enable(menu_SURVEY_RESTRICT, false);
388                menu.Enable(menu_SURVEY_HIDE, false);
389                menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
390                break;
391            case STATE_OFF:
392                menu.Enable(menu_SURVEY_HIDE, false);
393                menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
394                break;
395#endif
396        }
397        PopupMenu(&menu);
398    }
399    menu_data = NULL;
400    e.Skip();
401}
402
403bool AvenTreeCtrl::GetSelectionData(wxTreeItemData** data) const
404{
405    assert(m_Enabled);
406    assert(data);
407
408    if (!m_SelValid) {
409        return false;
410    }
411
412    wxTreeItemId id = GetSelection();
413    if (id.IsOk()) {
414        *data = GetItemData(id);
415    }
416
417    return id.IsOk() && *data;
418}
419
420void AvenTreeCtrl::UnselectAll()
421{
422    m_SelValid = false;
423    wxTreeCtrl::UnselectAll();
424}
425
426void AvenTreeCtrl::OnKeyPress(wxKeyEvent &e)
427{
428    switch (e.GetKeyCode()) {
429        case WXK_ESCAPE:
430            m_Parent->ClearTreeSelection();
431            break;
432        case WXK_RETURN: {
433            wxTreeItemId id = GetSelection();
434            if (id.IsOk()) {
435                if (ItemHasChildren(id)) {
436                    // If on a branch, expand/contract it.
437                    if (IsExpanded(id)) {
438                        Collapse(id);
439                    } else {
440                        Expand(id);
441                    }
442                } else {
443                    // If on a station, centre on it by selecting it twice.
444                    m_Parent->TreeItemSelected(GetItemData(id));
445                    m_Parent->TreeItemSelected(GetItemData(id));
446                }
447            }
448            break;
449        }
450        case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
451        case WXK_HOME: case WXK_END: case WXK_PAGEUP: case WXK_PAGEDOWN:
452            e.Skip();
453            break;
454        default:
455            // Pass key event to MainFrm which will pass to GfxCore which will
456            // pass to GUIControl.
457            m_Parent->OnKeyPress(e);
458            break;
459    }
460}
461
462void AvenTreeCtrl::OnRestrict(wxCommandEvent&)
463{
464    m_Parent->RestrictTo(menu_data ? menu_data->GetSurvey() : wxString());
465}
466
467void AvenTreeCtrl::OnHide(wxCommandEvent&)
468{
469    // Shouldn't be available for the root item.
470    wxASSERT(menu_data);
471    // Hide should be disabled unless the item is explicitly shown.
472    wxASSERT(GetItemState(menu_item) == STATE_ON);
473    SetItemState(menu_item, STATE_OFF);
474    filter.remove(menu_data->GetSurvey());
475#if 0
476    Freeze();
477    // Show siblings if not already shown or hidden.
478    wxTreeItemId i = menu_item;
479    while ((i = GetPrevSibling(i)).IsOk()) {
480        if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
481            SetItemState(i, 1);
482    }
483    i = menu_item;
484    while ((i = GetNextSibling(i)).IsOk()) {
485        if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
486            SetItemState(i, 1);
487    }
488    Thaw();
489#endif
490    m_Parent->ForceFullRedraw();
491}
492
493void AvenTreeCtrl::OnShow(wxCommandEvent&)
494{
495    // Shouldn't be available for the root item.
496    wxASSERT(menu_data);
497    auto old_state = GetItemState(menu_item);
498    // Show should be disabled for an explicitly shown item.
499    wxASSERT(old_state != STATE_ON);
500    Freeze();
501    SetItemState(menu_item, STATE_ON);
502    filter.add(menu_data->GetSurvey());
503    if (old_state == wxTREE_ITEMSTATE_NONE) {
504        // Hide siblings if not already shown or hidden.
505        wxTreeItemId i = menu_item;
506        while ((i = GetPrevSibling(i)).IsOk()) {
507            if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
508                const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
509                SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
510            }
511        }
512        i = menu_item;
513        while ((i = GetNextSibling(i)).IsOk()) {
514            if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
515                const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
516                SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
517            }
518        }
519    }
520    Thaw();
521    m_Parent->ForceFullRedraw();
522}
523
524void AvenTreeCtrl::OnHideSiblings(wxCommandEvent&)
525{
526    // Shouldn't be available for the root item.
527    wxASSERT(menu_data);
528    Freeze();
529    SetItemState(menu_item, STATE_ON);
530    filter.add(menu_data->GetSurvey());
531
532    wxTreeItemId i = menu_item;
533    while ((i = GetPrevSibling(i)).IsOk()) {
534        const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
535        filter.remove(data->GetSurvey());
536        SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
537    }
538    i = menu_item;
539    while ((i = GetNextSibling(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    Thaw();
545    m_Parent->ForceFullRedraw();
546}
547
548void AvenTreeCtrl::OnStateClick(wxTreeEvent& e)
549{
550    auto item = e.GetItem();
551    const TreeData* data = static_cast<const TreeData*>(GetItemData(item));
552    switch (GetItemState(item)) {
553        case STATE_BLANK:
554            // Click on blank state icon for a station - let the tree handle
555            // this in the same way as a click on the label.
556            return;
557        case STATE_ON:
558            filter.remove(data->GetSurvey());
559            SetItemState(item, STATE_OFF);
560            break;
561        case STATE_OFF:
562            filter.add(data->GetSurvey());
563            SetItemState(item, STATE_ON);
564            break;
565    }
566    e.Skip();
567    m_Parent->ForceFullRedraw();
568}
Note: See TracBrowser for help on using the repository browser.