Fix coding style
[survex.git] / src / aventreectrl.cc
blobd9eb387fe1dab3f68dc13fa5c59d51788b0b9504
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 assert(!bit.empty());
195 // Add the current tree ID to the stack.
196 previous_ids.push(current_id);
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);
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;
220 } else {
221 count = prefix.length() + 1;
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);
229 // Count the number of prefixes to ascend over.
230 int num_prefixes = prefixes_ascended.Freq(separator);
232 // Reverse up over these prefixes.
233 for (int i = 1; i <= num_prefixes; i++) {
234 previous_ids.pop();
236 current_id = previous_ids.top();
237 previous_ids.pop();
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;
245 // Extract the next bit of prefix.
246 next_dot = prefix.find(separator, prev_dot + 1);
248 wxString bit = prefix.substr(prev_dot, next_dot - prev_dot);
249 assert(!bit.empty());
251 // Add the current tree ID to the stack.
252 previous_ids.push(current_id);
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);
260 current_prefix = prefix;
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));
279 Expand(treeroot);
280 m_Enabled = true;
281 Thaw();
284 constexpr auto TREE_MASK = wxTREE_HITTEST_ONITEMLABEL |
285 wxTREE_HITTEST_ONITEMRIGHT |
286 wxTREE_HITTEST_ONITEMSTATEICON;
288 void AvenTreeCtrl::OnMouseMove(wxMouseEvent& event)
290 if (!m_Enabled || m_Parent->Animating())
291 return;
293 int flags;
294 wxTreeItemId pos = HitTest(event.GetPosition(), flags);
295 if (!(flags & TREE_MASK)) {
296 pos = wxTreeItemId();
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);
307 } else {
308 m_Parent->DisplayTreeInfo();
312 void AvenTreeCtrl::SetHere(wxTreeItemId pos)
314 if (pos == m_LastItem) return;
316 if (m_LastItem.IsOk()) {
317 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
319 if (pos.IsOk()) {
320 m_BackgroundColour = GetItemBackgroundColour(pos);
321 SetItemBackgroundColour(pos, wxColour(180, 180, 180));
323 m_LastItem = pos;
326 void AvenTreeCtrl::OnLeaveWindow(wxMouseEvent&)
328 if (m_LastItem.IsOk()) {
329 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
330 m_LastItem = wxTreeItemId();
332 m_Parent->DisplayTreeInfo();
335 void AvenTreeCtrl::OnSelChanged(wxTreeEvent&)
337 m_SelValid = true;
340 void AvenTreeCtrl::OnItemActivated(wxTreeEvent& e)
342 if (!m_Enabled) return;
344 m_Parent->TreeItemSelected(GetItemData(e.GetItem()));
347 void AvenTreeCtrl::OnMenu(wxTreeEvent& e)
349 if (!m_Enabled) return;
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.
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.
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
397 PopupMenu(&menu);
399 menu_data = NULL;
400 e.Skip();
403 bool AvenTreeCtrl::GetSelectionData(wxTreeItemData** data) const
405 assert(m_Enabled);
406 assert(data);
408 if (!m_SelValid) {
409 return false;
412 wxTreeItemId id = GetSelection();
413 if (id.IsOk()) {
414 *data = GetItemData(id);
417 return id.IsOk() && *data;
420 void AvenTreeCtrl::UnselectAll()
422 m_SelValid = false;
423 wxTreeCtrl::UnselectAll();
426 void AvenTreeCtrl::OnKeyPress(wxKeyEvent &e)
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);
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));
448 break;
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;
462 void AvenTreeCtrl::OnRestrict(wxCommandEvent&)
464 m_Parent->RestrictTo(menu_data ? menu_data->GetSurvey() : wxString());
467 void AvenTreeCtrl::OnHide(wxCommandEvent&)
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);
483 i = menu_item;
484 while ((i = GetNextSibling(i)).IsOk()) {
485 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
486 SetItemState(i, 1);
488 Thaw();
489 #endif
490 m_Parent->ForceFullRedraw();
493 void AvenTreeCtrl::OnShow(wxCommandEvent&)
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);
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);
520 Thaw();
521 m_Parent->ForceFullRedraw();
524 void AvenTreeCtrl::OnHideSiblings(wxCommandEvent&)
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());
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);
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);
544 Thaw();
545 m_Parent->ForceFullRedraw();
548 void AvenTreeCtrl::OnStateClick(wxTreeEvent& e)
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;
566 e.Skip();
567 m_Parent->ForceFullRedraw();