source: git/src/aventreectrl.cc@ 09ad6d7

RELEASE/1.2 debug-ci debug-ci-sanitisers faster-cavernlog log-select main stereo-2025 walls-data walls-data-hanging-as-warning warn-only-for-hanging-survey
Last change on this file since 09ad6d7 was 44e007d, checked in by Olly Betts <olly@…>, 8 years ago

Only show checkboxes for surveys not stations

  • Property mode set to 100644
File size: 15.3 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 m_Parent->DisplayTreeInfo(GetItemData(pos));
301 } else {
302 m_Parent->DisplayTreeInfo();
303 }
304}
305
306void AvenTreeCtrl::SetHere(wxTreeItemId pos)
307{
308 if (pos == m_LastItem) return;
309
310 if (m_LastItem.IsOk()) {
311 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
312 }
313 if (pos.IsOk()) {
314 m_BackgroundColour = GetItemBackgroundColour(pos);
315 SetItemBackgroundColour(pos, wxColour(180, 180, 180));
316 }
317 m_LastItem = pos;
318}
319
320void AvenTreeCtrl::OnLeaveWindow(wxMouseEvent&)
321{
322 if (m_LastItem.IsOk()) {
323 SetItemBackgroundColour(m_LastItem, m_BackgroundColour);
324 m_LastItem = wxTreeItemId();
325 }
326 m_Parent->DisplayTreeInfo();
327}
328
329void AvenTreeCtrl::OnSelChanged(wxTreeEvent&)
330{
331 m_SelValid = true;
332}
333
334void AvenTreeCtrl::OnItemActivated(wxTreeEvent& e)
335{
336 if (!m_Enabled) return;
337
338 m_Parent->TreeItemSelected(GetItemData(e.GetItem()));
339}
340
341void AvenTreeCtrl::OnMenu(wxTreeEvent& e)
342{
343 if (!m_Enabled) return;
344
345 const TreeData* data = static_cast<const TreeData*>(GetItemData(e.GetItem()));
346 menu_data = data;
347 menu_item = e.GetItem();
348 if (!data) {
349 // Root:
350 wxMenu menu;
351 /* TRANSLATORS: In aven's survey tree, right-clicking on the root
352 * gives a pop-up menu and this is an option (but only enabled if
353 * the view is restricted to a subsurvey). It reloads the current
354 * survey file with the who survey visible.
355 */
356 menu.Append(menu_SURVEY_SHOW_ALL, wmsg(/*Show all*/245));
357 if (m_Parent->GetSurvey().empty())
358 menu.Enable(menu_SURVEY_SHOW_ALL, false);
359 PopupMenu(&menu);
360 } else if (data->GetLabel()) {
361 // Station: name is data->GetLabel()->GetText()
362 } else {
363 // Survey:
364 wxMenu menu;
365 /* TRANSLATORS: In aven's survey tree, right-clicking on a survey
366 * name gives a pop-up menu and this is an option. It reloads the
367 * current survey file with the view restricted to the survey
368 * clicked upon.
369 */
370 menu.Append(menu_SURVEY_RESTRICT, wmsg(/*Hide others*/246));
371 menu.AppendSeparator();
372 //menu.Append(menu_SURVEY_HIDE, wmsg(/*&Hide*/407));
373 menu.Append(menu_SURVEY_SHOW, wmsg(/*&Show*/409));
374 //menu.Append(menu_SURVEY_HIDE_SIBLINGS, wmsg(/*Hide si&blings*/388));
375 switch (GetItemState(menu_item)) {
376 case STATE_ON: // Currently shown.
377 menu.Enable(menu_SURVEY_SHOW, false);
378 break;
379#if 0
380 case STATE_HIDDEN: // Currently hidden.
381 menu.Enable(menu_SURVEY_RESTRICT, false);
382 menu.Enable(menu_SURVEY_HIDE, false);
383 menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
384 break;
385 case STATE_OFF:
386 menu.Enable(menu_SURVEY_HIDE, false);
387 menu.Enable(menu_SURVEY_HIDE_SIBLINGS, false);
388 break;
389#endif
390 }
391 PopupMenu(&menu);
392 }
393 menu_data = NULL;
394 e.Skip();
395}
396
397bool AvenTreeCtrl::GetSelectionData(wxTreeItemData** data) const
398{
399 assert(m_Enabled);
400 assert(data);
401
402 if (!m_SelValid) {
403 return false;
404 }
405
406 wxTreeItemId id = GetSelection();
407 if (id.IsOk()) {
408 *data = GetItemData(id);
409 }
410
411 return id.IsOk() && *data;
412}
413
414void AvenTreeCtrl::UnselectAll()
415{
416 m_SelValid = false;
417 wxTreeCtrl::UnselectAll();
418}
419
420void AvenTreeCtrl::OnKeyPress(wxKeyEvent &e)
421{
422 switch (e.GetKeyCode()) {
423 case WXK_ESCAPE:
424 m_Parent->ClearTreeSelection();
425 break;
426 case WXK_RETURN: {
427 wxTreeItemId id = GetSelection();
428 if (id.IsOk()) {
429 if (ItemHasChildren(id)) {
430 // If on a branch, expand/contract it.
431 if (IsExpanded(id)) {
432 Collapse(id);
433 } else {
434 Expand(id);
435 }
436 } else {
437 // If on a station, centre on it by selecting it twice.
438 m_Parent->TreeItemSelected(GetItemData(id));
439 m_Parent->TreeItemSelected(GetItemData(id));
440 }
441 }
442 break;
443 }
444 case WXK_LEFT: case WXK_RIGHT: case WXK_UP: case WXK_DOWN:
445 case WXK_HOME: case WXK_END: case WXK_PAGEUP: case WXK_PAGEDOWN:
446 e.Skip();
447 break;
448 default:
449 // Pass key event to MainFrm which will pass to GfxCore which will
450 // pass to GUIControl.
451 m_Parent->OnKeyPress(e);
452 break;
453 }
454}
455
456void AvenTreeCtrl::OnRestrict(wxCommandEvent&)
457{
458 m_Parent->RestrictTo(menu_data ? menu_data->GetSurvey() : wxString());
459}
460
461void AvenTreeCtrl::OnHide(wxCommandEvent&)
462{
463 // Shouldn't be available for the root item.
464 wxASSERT(menu_data);
465 // Hide should be disabled unless the item is explicitly shown.
466 wxASSERT(GetItemState(menu_item) == STATE_ON);
467 SetItemState(menu_item, STATE_OFF);
468 filter.remove(menu_data->GetSurvey());
469#if 0
470 Freeze();
471 // Show siblings if not already shown or hidden.
472 wxTreeItemId i = menu_item;
473 while ((i = GetPrevSibling(i)).IsOk()) {
474 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
475 SetItemState(i, 1);
476 }
477 i = menu_item;
478 while ((i = GetNextSibling(i)).IsOk()) {
479 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE)
480 SetItemState(i, 1);
481 }
482 Thaw();
483#endif
484 m_Parent->ForceFullRedraw();
485}
486
487void AvenTreeCtrl::OnShow(wxCommandEvent&)
488{
489 // Shouldn't be available for the root item.
490 wxASSERT(menu_data);
491 auto old_state = GetItemState(menu_item);
492 // Show should be disabled for an explicitly shown item.
493 wxASSERT(old_state != STATE_ON);
494 Freeze();
495 SetItemState(menu_item, STATE_ON);
496 filter.add(menu_data->GetSurvey());
497 if (old_state == wxTREE_ITEMSTATE_NONE) {
498 // Hide siblings if not already shown or hidden.
499 wxTreeItemId i = menu_item;
500 while ((i = GetPrevSibling(i)).IsOk()) {
501 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
502 const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
503 SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
504 }
505 }
506 i = menu_item;
507 while ((i = GetNextSibling(i)).IsOk()) {
508 if (GetItemState(i) == wxTREE_ITEMSTATE_NONE) {
509 const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
510 SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
511 }
512 }
513 }
514 Thaw();
515 m_Parent->ForceFullRedraw();
516}
517
518void AvenTreeCtrl::OnHideSiblings(wxCommandEvent&)
519{
520 // Shouldn't be available for the root item.
521 wxASSERT(menu_data);
522 Freeze();
523 SetItemState(menu_item, STATE_ON);
524 filter.add(menu_data->GetSurvey());
525
526 wxTreeItemId i = menu_item;
527 while ((i = GetPrevSibling(i)).IsOk()) {
528 const TreeData* data = static_cast<const TreeData*>(GetItemData(i));
529 filter.remove(data->GetSurvey());
530 SetItemState(i, data->IsStation() ? STATE_BLANK : STATE_OFF);
531 }
532 i = menu_item;
533 while ((i = GetNextSibling(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 Thaw();
539 m_Parent->ForceFullRedraw();
540}
541
542void AvenTreeCtrl::OnStateClick(wxTreeEvent& e)
543{
544 auto item = e.GetItem();
545 const TreeData* data = static_cast<const TreeData*>(GetItemData(item));
546 switch (GetItemState(item)) {
547 case STATE_BLANK:
548 // Click on blank state icon for a station - let the tree handle
549 // this in the same way as a click on the label.
550 return;
551 case STATE_ON:
552 filter.remove(data->GetSurvey());
553 SetItemState(item, STATE_OFF);
554 break;
555 case STATE_OFF:
556 filter.add(data->GetSurvey());
557 SetItemState(item, STATE_ON);
558 break;
559 }
560 e.Skip();
561 m_Parent->ForceFullRedraw();
562}
Note: See TracBrowser for help on using the repository browser.