Show location for backsights out of tolerance
[survex.git] / src / aventreectrl.cc
bloba7da137d83373a9a70d64a37ac1b5f85ccccdbf4
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.
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.
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
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include "aventreectrl.h"
30 #include "mainfrm.h"
32 #include <stack>
34 using namespace std;
36 // STATE_BLANK is used for stations which are siblings of surveys which have
37 // select checkboxes.
38 enum { STATE_BLANK = 0, STATE_OFF, STATE_ON };
40 /* XPM */
41 static 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 " "
63 /* XPM */
64 static 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 " "
87 /* XPM */
88 static 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 " ............ ",
112 BEGIN_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)
125 END_EVENT_TABLE()
127 AvenTreeCtrl::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)
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);
143 void AvenTreeCtrl::FillTree(const wxString& root_name)
145 Freeze();
146 m_Enabled = false;
147 m_LastItem = wxTreeItemId();
148 m_SelValid = false;
149 DeleteAllItems();
151 const wxChar separator = m_Parent->GetSeparator();
152 filter.clear();
153 filter.SetSeparator(separator);
155 // Create the root of the tree.
156 wxTreeItemId treeroot = AddRoot(root_name);
158 // Fill the tree of stations and prefixes.
159 stack<wxTreeItemId> previous_ids;
160 wxString current_prefix;
161 wxTreeItemId current_id = treeroot;
163 list<LabelInfo*>::const_iterator pos = m_Parent->GetLabels();
164 while (pos != m_Parent->GetLabelsEnd()) {
165 LabelInfo* label = *pos++;
167 if (label->IsAnon()) continue;
169 // Determine the current prefix.
170 wxString prefix = label->GetText().BeforeLast(separator);
172 // Determine if we're still on the same prefix.
173 if (prefix == current_prefix) {
174 // no need to fiddle with branches...
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;
189 // Extract the next bit of prefix.
190 next_dot = prefix.find(separator, prev_dot + 1);
192 wxString bit = prefix.substr(prev_dot, next_dot - prev_dot);
193 // Sigh, therion can produce files with empty components in
194 // station names!
195 // assert(!bit.empty());
197 // Add the current tree ID to the stack.
198 previous_ids.push(current_id);
200 // Append the new item to the tree and set this as the current branch.
201 current_id = AppendItem(current_id, bit);
202 SetItemData(current_id, new TreeData(prefix.substr(0, next_dot)));
203 } while (next_dot != wxString::npos);
205 // Otherwise, we must have moved up, and possibly then down again.
206 else {
207 size_t count = 0;
208 bool ascent_only = (prefix.length() < current_prefix.length() &&
209 current_prefix.StartsWith(prefix) &&
210 (current_prefix[prefix.length()] == separator ||
211 prefix.empty()));
212 if (!ascent_only) {
213 // Find out how much of the current prefix and the new prefix
214 // are the same.
215 // Note that we require a match of a whole number of parts
216 // between dots!
217 size_t n = min(prefix.length(), current_prefix.length());
218 size_t i;
219 for (i = 0; i < n && prefix[i] == current_prefix[i]; ++i) {
220 if (prefix[i] == separator) count = i + 1;
222 } else {
223 count = prefix.length() + 1;
226 // Extract the part of the current prefix after the bit (if any)
227 // which has matched.
228 // This gives the prefixes to ascend over.
229 wxString prefixes_ascended = current_prefix.substr(count);
231 // Count the number of prefixes to ascend over.
232 int num_prefixes = prefixes_ascended.Freq(separator);
234 // Reverse up over these prefixes.
235 for (int i = 1; i <= num_prefixes; i++) {
236 previous_ids.pop();
238 current_id = previous_ids.top();
239 previous_ids.pop();
241 if (!ascent_only) {
242 // Add branches for this new part.
243 size_t next_dot = count - 1;
244 do {
245 size_t prev_dot = next_dot + 1;
247 // Extract the next bit of prefix.
248 next_dot = prefix.find(separator, prev_dot + 1);
250 wxString bit = prefix.substr(prev_dot, next_dot - prev_dot);
251 // Sigh, therion can produce files with empty components in
252 // station names!
253 // assert(!bit.empty());
255 // Add the current tree ID to the stack.
256 previous_ids.push(current_id);
258 // Append the new item to the tree and set this as the current branch.
259 current_id = AppendItem(current_id, bit);
260 SetItemData(current_id, new TreeData(prefix.substr(0, next_dot)));
261 } while (next_dot != wxString::npos);
264 current_prefix = prefix;
267 // Now add the leaf.
268 wxString bit = label->GetText().AfterLast(separator);
269 // Sigh, therion can produce files with empty components in station
270 // names!
271 // assert(!bit.empty());
272 wxTreeItemId id = AppendItem(current_id, bit);
273 SetItemData(id, new TreeData(label));
274 label->tree_id = id;
275 // Set the colour for an item in the survey tree.
276 if (label->IsEntrance()) {
277 // Entrances are green (like entrance blobs).
278 SetItemTextColour(id, wxColour(0, 255, 40));
279 } else if (label->IsSurface()) {
280 // Surface stations are dark green.
281 SetItemTextColour(id, wxColour(49, 158, 79));
285 Expand(treeroot);
286 m_Enabled = true;
287 Thaw();
290 constexpr auto TREE_MASK = wxTREE_HITTEST_ONITEMLABEL |
291 wxTREE_HITTEST_ONITEMRIGHT |
292 wxTREE_HITTEST_ONITEMSTATEICON;
294 void AvenTreeCtrl::OnMouseMove(wxMouseEvent& event)
296 if (!m_Enabled || m_Parent->Animating())
297 return;
299 int flags;
300 wxTreeItemId pos = HitTest(event.GetPosition(), flags);
301 if (!(flags & TREE_MASK)) {
302 pos = wxTreeItemId();
304 if (pos == m_LastItem) return;
305 if (pos.IsOk()) {
306 const TreeData* data = static_cast<const TreeData*>(GetItemData(pos));
307 m_Parent->DisplayTreeInfo(data);
308 if (data && !data->IsStation()) {
309 // For stations, MainFrm calls back to SetHere(), but for surveys
310 // we need to do that ourselves.
311 SetHere(pos);
313 } else {
314 m_Parent->DisplayTreeInfo();
318 void AvenTreeCtrl::SetHere(wxTreeItemId pos)
320 if (pos == m_LastItem) return;
322 if (m_LastItem.IsOk()) {
323 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
325 if (pos.IsOk()) {
326 m_BackgroundColour = GetItemBackgroundColour(pos);
327 SetItemBackgroundColour(pos, wxColour(180, 180, 180));
329 m_LastItem = pos;
332 void AvenTreeCtrl::OnLeaveWindow(wxMouseEvent&)
334 if (m_LastItem.IsOk()) {
335 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
336 m_LastItem = wxTreeItemId();
338 m_Parent->DisplayTreeInfo();
341 void AvenTreeCtrl::OnSelChanged(wxTreeEvent&)
343 m_SelValid = true;
346 void AvenTreeCtrl::OnItemActivated(wxTreeEvent& e)
348 if (!m_Enabled) return;
350 m_Parent->TreeItemSelected(GetItemData(e.GetItem()));
353 void AvenTreeCtrl::OnMenu(wxTreeEvent& e)
355 if (!m_Enabled) return;
357 const TreeData* data = static_cast<const TreeData*>(GetItemData(e.GetItem()));
358 menu_data = data;
359 menu_item = e.GetItem();
360 if (!data) {
361 // Root:
362 wxMenu menu;
363 /* TRANSLATORS: In aven's survey tree, right-clicking on the root
364 * gives a pop-up menu and this is an option (but only enabled if
365 * the view is restricted to a subsurvey). It reloads the current
366 * survey file with the who survey visible.
368 menu.Append(menu_SURVEY_SHOW_ALL, wmsg(/*Show all*/245));
369 if (m_Parent->GetSurvey().empty())
370 menu.Enable(menu_SURVEY_SHOW_ALL, false);
371 PopupMenu(&menu);
372 } else if (data->GetLabel()) {
373 // Station: name is data->GetLabel()->GetText()
374 } else {
375 // Survey:
376 wxMenu menu;
377 /* TRANSLATORS: In aven's survey tree, right-clicking on a survey
378 * name gives a pop-up menu and this is an option. It reloads the
379 * current survey file with the view restricted to the survey
380 * clicked upon.
382 menu.Append(menu_SURVEY_RESTRICT, wmsg(/*Hide others*/246));
383 menu.AppendSeparator();
384 //menu.Append(menu_SURVEY_HIDE, wmsg(/*&Hide*/407));
385 menu.Append(menu_SURVEY_SHOW, wmsg(/*&Show*/409));
386 //menu.Append(menu_SURVEY_HIDE_SIBLINGS, wmsg(/*Hide si&blings*/388));
387 switch (GetItemState(menu_item)) {
388 case STATE_ON: // Currently shown.
389 menu.Enable(menu_SURVEY_SHOW, false);
390 break;
391 #if 0
392 case STATE_HIDDEN: // Currently hidden.
393 menu.Enable(menu_SURVEY_RESTRICT, false);
394 menu.Enable(menu_SURVEY_HIDE, false);
395 menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
396 break;
397 case STATE_OFF:
398 menu.Enable(menu_SURVEY_HIDE, false);
399 menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
400 break;
401 #endif
403 PopupMenu(&menu);
405 menu_data = NULL;
406 e.Skip();
409 bool AvenTreeCtrl::GetSelectionData(wxTreeItemData** data) const
411 assert(m_Enabled);
412 assert(data);
414 if (!m_SelValid) {
415 return false;
418 wxTreeItemId id = GetSelection();
419 if (id.IsOk()) {
420 *data = GetItemData(id);
423 return id.IsOk() && *data;
426 void AvenTreeCtrl::UnselectAll()
428 m_SelValid = false;
429 wxTreeCtrl::UnselectAll();
432 void AvenTreeCtrl::OnKeyPress(wxKeyEvent &e)
434 switch (e.GetKeyCode()) {
435 case WXK_ESCAPE:
436 m_Parent->ClearTreeSelection();
437 break;
438 case WXK_RETURN: {
439 wxTreeItemId id = GetSelection();
440 if (id.IsOk()) {
441 if (ItemHasChildren(id)) {
442 // If on a branch, expand/contract it.
443 if (IsExpanded(id)) {
444 Collapse(id);
445 } else {
446 Expand(id);
448 } else {
449 // If on a station, centre on it by selecting it twice.
450 m_Parent->TreeItemSelected(GetItemData(id));
451 m_Parent->TreeItemSelected(GetItemData(id));
454 break;
456 case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
457 case WXK_HOME: case WXK_END: case WXK_PAGEUP: case WXK_PAGEDOWN:
458 e.Skip();
459 break;
460 default:
461 // Pass key event to MainFrm which will pass to GfxCore which will
462 // pass to GUIControl.
463 m_Parent->OnKeyPress(e);
464 break;
468 void AvenTreeCtrl::OnRestrict(wxCommandEvent&)
470 m_Parent->RestrictTo(menu_data ? menu_data->GetSurvey() : wxString());
473 void AvenTreeCtrl::OnHide(wxCommandEvent&)
475 // Shouldn't be available for the root item.
476 wxASSERT(menu_data);
477 // Hide should be disabled unless the item is explicitly shown.
478 wxASSERT(GetItemState(menu_item) == STATE_ON);
479 SetItemState(menu_item, STATE_OFF);
480 filter.remove(menu_data->GetSurvey());
481 #if 0
482 Freeze();
483 // Show siblings if not already shown or hidden.
484 wxTreeItemId i = menu_item;
485 while ((i = GetPrevSibling(i)).IsOk()) {
486 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
487 SetItemState(i, 1);
489 i = menu_item;
490 while ((i = GetNextSibling(i)).IsOk()) {
491 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
492 SetItemState(i, 1);
494 Thaw();
495 #endif
496 m_Parent->ForceFullRedraw();
499 void AvenTreeCtrl::OnShow(wxCommandEvent&)
501 // Shouldn't be available for the root item.
502 wxASSERT(menu_data);
503 auto old_state = GetItemState(menu_item);
504 // Show should be disabled for an explicitly shown item.
505 wxASSERT(old_state != STATE_ON);
506 Freeze();
507 SetItemState(menu_item, STATE_ON);
508 filter.add(menu_data->GetSurvey());
509 if (old_state == wxTREE_ITEMSTATE_NONE) {
510 // Hide siblings if not already shown or hidden.
511 wxTreeItemId i = menu_item;
512 while ((i = GetPrevSibling(i)).IsOk()) {
513 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
514 const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
515 SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
518 i = menu_item;
519 while ((i = GetNextSibling(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);
526 Thaw();
527 m_Parent->ForceFullRedraw();
530 void AvenTreeCtrl::OnHideSiblings(wxCommandEvent&)
532 // Shouldn't be available for the root item.
533 wxASSERT(menu_data);
534 Freeze();
535 SetItemState(menu_item, STATE_ON);
536 filter.add(menu_data->GetSurvey());
538 wxTreeItemId i = menu_item;
539 while ((i = GetPrevSibling(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);
544 i = menu_item;
545 while ((i = GetNextSibling(i)).IsOk()) {
546 const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
547 filter.remove(data->GetSurvey());
548 SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
550 Thaw();
551 m_Parent->ForceFullRedraw();
554 void AvenTreeCtrl::OnStateClick(wxTreeEvent& e)
556 auto item = e.GetItem();
557 const TreeData* data = static_cast<const TreeData*>(GetItemData(item));
558 switch (GetItemState(item)) {
559 case STATE_BLANK:
560 // Click on blank state icon for a station - let the tree handle
561 // this in the same way as a click on the label.
562 return;
563 case STATE_ON:
564 filter.remove(data->GetSurvey());
565 SetItemState(item, STATE_OFF);
566 break;
567 case STATE_OFF:
568 filter.add(data->GetSurvey());
569 SetItemState(item, STATE_ON);
570 break;
572 e.Skip();
573 m_Parent->ForceFullRedraw();