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 // Sigh, therion can produce files with empty components in
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.
208 bool ascent_only
= (prefix
.length() < current_prefix
.length() &&
209 current_prefix
.StartsWith(prefix
) &&
210 (current_prefix
[prefix
.length()] == separator
||
213 // Find out how much of the current prefix and the new prefix
215 // Note that we require a match of a whole number of parts
217 size_t n
= min(prefix
.length(), current_prefix
.length());
219 for (i
= 0; i
< n
&& prefix
[i
] == current_prefix
[i
]; ++i
) {
220 if (prefix
[i
] == separator
) count
= i
+ 1;
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
++) {
238 current_id
= previous_ids
.top();
242 // Add branches for this new part.
243 size_t next_dot
= count
- 1;
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
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
;
268 wxString bit
= label
->GetText().AfterLast(separator
);
269 // Sigh, therion can produce files with empty components in station
271 // assert(!bit.empty());
272 wxTreeItemId id
= AppendItem(current_id
, bit
);
273 SetItemData(id
, new TreeData(label
));
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));
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())
300 wxTreeItemId pos
= HitTest(event
.GetPosition(), flags
);
301 if (!(flags
& TREE_MASK
)) {
302 pos
= wxTreeItemId();
304 if (pos
== m_LastItem
) return;
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.
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
);
326 m_BackgroundColour
= GetItemBackgroundColour(pos
);
327 SetItemBackgroundColour(pos
, wxColour(180, 180, 180));
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
&)
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()));
359 menu_item
= e
.GetItem();
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);
372 } else if (data
->GetLabel()) {
373 // Station: name is data->GetLabel()->GetText()
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
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);
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);
398 menu
.Enable(menu_SURVEY_HIDE
, false);
399 menu
.Enable(menu_SURVEY_HIDE_SIBLINGS
, false);
409 bool AvenTreeCtrl::GetSelectionData(wxTreeItemData
** data
) const
418 wxTreeItemId id
= GetSelection();
420 *data
= GetItemData(id
);
423 return id
.IsOk() && *data
;
426 void AvenTreeCtrl::UnselectAll()
429 wxTreeCtrl::UnselectAll();
432 void AvenTreeCtrl::OnKeyPress(wxKeyEvent
&e
)
434 switch (e
.GetKeyCode()) {
436 m_Parent
->ClearTreeSelection();
439 wxTreeItemId id
= GetSelection();
441 if (ItemHasChildren(id
)) {
442 // If on a branch, expand/contract it.
443 if (IsExpanded(id
)) {
449 // If on a station, centre on it by selecting it twice.
450 m_Parent
->TreeItemSelected(GetItemData(id
));
451 m_Parent
->TreeItemSelected(GetItemData(id
));
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
:
461 // Pass key event to MainFrm which will pass to GfxCore which will
462 // pass to GUIControl.
463 m_Parent
->OnKeyPress(e
);
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.
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());
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
)
490 while ((i
= GetNextSibling(i
)).IsOk()) {
491 if (GetItemState(i
) == wxTREE_ITEMSTATE_NONE
)
496 m_Parent
->ForceFullRedraw();
499 void AvenTreeCtrl::OnShow(wxCommandEvent
&)
501 // Shouldn't be available for the root item.
503 auto old_state
= GetItemState(menu_item
);
504 // Show should be disabled for an explicitly shown item.
505 wxASSERT(old_state
!= STATE_ON
);
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
);
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
);
527 m_Parent
->ForceFullRedraw();
530 void AvenTreeCtrl::OnHideSiblings(wxCommandEvent
&)
532 // Shouldn't be available for the root item.
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
);
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
);
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
)) {
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.
564 filter
.remove(data
->GetSurvey());
565 SetItemState(item
, STATE_OFF
);
568 filter
.add(data
->GetSurvey());
569 SetItemState(item
, STATE_ON
);
573 m_Parent
->ForceFullRedraw();