source: git/src/aventreectrl.cc@ 6969b17

main stereo-2025 warn-only-for-hanging-survey
Last change on this file since 6969b17 was 6969b17, checked in by Olly Betts <olly@…>, 2 years ago

Use wxID_ANY instead of literal -1

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