4 // Tree control used for the survey tree.
6 // Copyright (C) 2001, Mark R. Shinwell.
7 // Copyright (C) 2001-2003,2005,2006,2016,2018 Olly Betts
8 // Copyright (C) 2005 Martin Green
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
29 #include "aventreectrl.h"
36 // STATE_BLANK is used for stations which are siblings of surveys which have
38 enum { STATE_BLANK
= 0, STATE_OFF
, STATE_ON
};
41 static const char *blank_xpm
[] = {
42 /* columns rows colors chars-per-pixel */
64 static const char *off_xpm
[] = {
65 /* columns rows colors chars-per-pixel */
88 static const char *on_xpm
[] = {
89 /* columns rows colors chars-per-pixel */
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
)
127 AvenTreeCtrl::AvenTreeCtrl(MainFrm
* parent
, wxWindow
* window_parent
) :
128 wxTreeCtrl(window_parent
, -1),
132 m_BackgroundColour(),
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
)
147 m_LastItem
= wxTreeItemId();
151 const wxChar separator
= m_Parent
->GetSeparator();
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
;
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.
206 bool ascent_only
= (prefix
.length() < current_prefix
.length() &&
207 current_prefix
.StartsWith(prefix
) &&
208 (current_prefix
[prefix
.length()] == separator
||
211 // Find out how much of the current prefix and the new prefix
213 // Note that we require a match of a whole number of parts
215 size_t n
= min(prefix
.length(), current_prefix
.length());
217 for (i
= 0; i
< n
&& prefix
[i
] == current_prefix
[i
]; ++i
) {
218 if (prefix
[i
] == separator
) count
= i
+ 1;
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
++) {
236 current_id
= previous_ids
.top();
240 // Add branches for this new part.
241 size_t next_dot
= count
- 1;
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
;
264 wxString bit
= label
->GetText().AfterLast(separator
);
265 assert(!bit
.empty());
266 wxTreeItemId id
= AppendItem(current_id
, bit
);
267 SetItemData(id
, new TreeData(label
));
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));
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())
294 wxTreeItemId pos
= HitTest(event
.GetPosition(), flags
);
295 if (!(flags
& TREE_MASK
)) {
296 pos
= wxTreeItemId();
298 if (pos
== m_LastItem
) return;
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.
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
);
320 m_BackgroundColour
= GetItemBackgroundColour(pos
);
321 SetItemBackgroundColour(pos
, wxColour(180, 180, 180));
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
&)
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()));
353 menu_item
= e
.GetItem();
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);
366 } else if (data
->GetLabel()) {
367 // Station: name is data->GetLabel()->GetText()
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
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);
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);
392 menu
.Enable(menu_SURVEY_HIDE
, false);
393 menu
.Enable(menu_SURVEY_HIDE_SIBLINGS
, false);
403 bool AvenTreeCtrl::GetSelectionData(wxTreeItemData
** data
) const
412 wxTreeItemId id
= GetSelection();
414 *data
= GetItemData(id
);
417 return id
.IsOk() && *data
;
420 void AvenTreeCtrl::UnselectAll()
423 wxTreeCtrl::UnselectAll();
426 void AvenTreeCtrl::OnKeyPress(wxKeyEvent
&e
)
428 switch (e
.GetKeyCode()) {
430 m_Parent
->ClearTreeSelection();
433 wxTreeItemId id
= GetSelection();
435 if (ItemHasChildren(id
)) {
436 // If on a branch, expand/contract it.
437 if (IsExpanded(id
)) {
443 // If on a station, centre on it by selecting it twice.
444 m_Parent
->TreeItemSelected(GetItemData(id
));
445 m_Parent
->TreeItemSelected(GetItemData(id
));
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
:
455 // Pass key event to MainFrm which will pass to GfxCore which will
456 // pass to GUIControl.
457 m_Parent
->OnKeyPress(e
);
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.
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());
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
)
484 while ((i
= GetNextSibling(i
)).IsOk()) {
485 if (GetItemState(i
) == wxTREE_ITEMSTATE_NONE
)
490 m_Parent
->ForceFullRedraw();
493 void AvenTreeCtrl::OnShow(wxCommandEvent
&)
495 // Shouldn't be available for the root item.
497 auto old_state
= GetItemState(menu_item
);
498 // Show should be disabled for an explicitly shown item.
499 wxASSERT(old_state
!= STATE_ON
);
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
);
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
);
521 m_Parent
->ForceFullRedraw();
524 void AvenTreeCtrl::OnHideSiblings(wxCommandEvent
&)
526 // Shouldn't be available for the root item.
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
);
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
);
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
)) {
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.
558 filter
.remove(data
->GetSurvey());
559 SetItemState(item
, STATE_OFF
);
562 filter
.add(data
->GetSurvey());
563 SetItemState(item
, STATE_ON
);
567 m_Parent
->ForceFullRedraw();