source: git/src/aventreectrl.cc

log-select
Last change on this file was 4c83f84, checked in by Olly Betts <olly@…>, 2 months ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

  • 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(-1, AvenTreeCtrl::OnSelChanged)
114    EVT_TREE_ITEM_ACTIVATED(-1, AvenTreeCtrl::OnItemActivated)
115    EVT_CHAR(AvenTreeCtrl::OnKeyPress)
116    EVT_TREE_ITEM_MENU(-1, 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(-1, AvenTreeCtrl::OnStateClick)
123END_EVENT_TABLE()
124
125AvenTreeCtrl::AvenTreeCtrl(MainFrm* parent, wxWindow* window_parent) :
126    wxTreeCtrl(window_parent, -1),
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.