Added the ability to hide/show entities in the outliner

This commit is contained in:
Justin Marshall
2026-05-06 15:04:39 -07:00
parent c1defbad40
commit 7c5ddb4336
3 changed files with 557 additions and 7 deletions
+168 -2
View File
@@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms
#include "Radiant.h"
#include "XYWnd.h"
#include "CamWnd.h"
#include "OutlinerDlg.h"
#include "splines.h"
#include <GL/glu.h>
@@ -826,6 +827,163 @@ static void CameraNav_DrawOverlay(CCamWnd* cam) {
}
/*
========================
Outliner-aware entity target/path lines
The original DrawPathLines() does not know about the outliner visibility list.
When no entity is hidden, keep using the original function so the normal editor
style is unchanged. When something is hidden, draw a filtered equivalent that
skips links where either endpoint is hidden.
========================
*/
static bool CamWnd_IsTargetLineKey(const char* key) {
if (key == NULL || key[0] == '\0') {
return false;
}
if (idStr::Icmp(key, "targetname") == 0) {
return false;
}
if (idStr::Icmp(key, "next") == 0) {
return true;
}
return idStr::Icmpn(key, "target", 6) == 0;
}
static bool CamWnd_EntityNameMatches(entity_t* ent, const char* name) {
if (ent == NULL || name == NULL || name[0] == '\0') {
return false;
}
const char* entName = ValueForKey(ent, "name");
if (entName != NULL && entName[0] != '\0' && idStr::Icmp(entName, name) == 0) {
return true;
}
const char* targetName = ValueForKey(ent, "targetname");
if (targetName != NULL && targetName[0] != '\0' && idStr::Icmp(targetName, name) == 0) {
return true;
}
return false;
}
static bool CamWnd_GetEntityLinePoint(entity_t* ent, idVec3& point) {
if (ent == NULL) {
return false;
}
if (GetVectorForKey(ent, "origin", point)) {
return true;
}
idBounds bounds;
bounds.Clear();
bool haveBounds = false;
for (brush_t* brush = ent->brushes.onext; brush != NULL && brush != &ent->brushes; brush = brush->onext) {
bounds.AddPoint(brush->mins);
bounds.AddPoint(brush->maxs);
haveBounds = true;
}
if (haveBounds) {
point = bounds.GetCenter();
return true;
}
return false;
}
static void CamWnd_DrawVisibleTargetLine(entity_t* source, entity_t* target) {
if (source == NULL || target == NULL || source == target) {
return;
}
if (!Outliner_IsEntityVisible(source) || !Outliner_IsEntityVisible(target)) {
return;
}
idVec3 start;
idVec3 end;
if (!CamWnd_GetEntityLinePoint(source, start) || !CamWnd_GetEntityLinePoint(target, end)) {
return;
}
glVertex3fv(start.ToFloatPtr());
glVertex3fv(end.ToFloatPtr());
}
static void CamWnd_DrawVisibleTargetLinesForName(entity_t* source, const char* targetName) {
if (targetName == NULL || targetName[0] == '\0') {
return;
}
if (world_entity != NULL && CamWnd_EntityNameMatches(world_entity, targetName)) {
CamWnd_DrawVisibleTargetLine(source, world_entity);
}
if (entities.next == NULL) {
return;
}
for (entity_t* target = entities.next; target != &entities; target = target->next) {
if (CamWnd_EntityNameMatches(target, targetName)) {
CamWnd_DrawVisibleTargetLine(source, target);
}
}
}
static void CamWnd_DrawVisibleTargetLinesForEntity(entity_t* source) {
if (source == NULL || !Outliner_IsEntityVisible(source)) {
return;
}
const int numKeys = source->epairs.GetNumKeyVals();
for (int keyIndex = 0; keyIndex < numKeys; keyIndex++) {
const idKeyValue* kv = source->epairs.GetKeyVal(keyIndex);
if (kv == NULL) {
continue;
}
if (!CamWnd_IsTargetLineKey(kv->GetKey().c_str())) {
continue;
}
CamWnd_DrawVisibleTargetLinesForName(source, kv->GetValue().c_str());
}
}
static void CamWnd_DrawVisiblePathLines() {
if (!Outliner_HasHiddenEntities()) {
DrawPathLines();
return;
}
glPushAttrib(GL_CURRENT_BIT);
globalImages->BindNull();
glDisable(GL_BLEND);
glLineWidth(1.0f);
glColor3f(1.0f, 0.75f, 0.0f);
glBegin(GL_LINES);
if (world_entity != NULL) {
CamWnd_DrawVisibleTargetLinesForEntity(world_entity);
}
if (entities.next != NULL) {
for (entity_t* source = entities.next; source != &entities; source = source->next) {
CamWnd_DrawVisibleTargetLinesForEntity(source);
}
}
glEnd();
glPopAttrib();
}
/*
========================
Camera window embedded menu bar
@@ -1213,7 +1371,15 @@ static void CamWnd_UpdateRuntimeState(CCamWnd* cam, bool renderMode, bool rebuil
}
static bool CamWnd_MenuFilterBrush(CCamWnd* cam, brush_t* brush) {
if (!cam || !brush) {
if (!brush) {
return false;
}
if (brush->owner != NULL && !Outliner_IsEntityVisible(brush->owner)) {
return true;
}
if (!cam) {
return false;
}
@@ -2475,7 +2641,7 @@ void CCamWnd::Cam_Draw() {
// draw pointfile
glEnable(GL_DEPTH_TEST);
DrawPathLines();
CamWnd_DrawVisiblePathLines();
if (g_qeglobals.d_pointfile_display_list) {
Pointfile_Draw();
+367 -4
View File
@@ -42,6 +42,144 @@ static const int OUTLINER_FILTER_LABEL_W = 42;
static const int OUTLINER_FILTER_H = 22;
static const int OUTLINER_GAP = 6;
static CPtrArray s_outlinerHiddenEntities;
static CString s_outlinerVisibilityMapName;
static entity_t* s_outlinerVisibilityWorldEntity = NULL;
static entity_t* s_outlinerVisibilityFirstEntity = NULL;
static int s_outlinerVisibilityEntityCount = -1;
static int s_outlinerVisibilityMapModified = -999999;
static bool s_outlinerVisibilityInitialized = false;
static const char* Outliner_CurrentMapName() {
return currentmap ? currentmap : "";
}
static entity_t* Outliner_FirstMapEntity() {
if (entities.next != NULL && entities.next != &entities) {
return entities.next;
}
return NULL;
}
static bool Outliner_ArrayContainsPtr(const CPtrArray& array, entity_t* ent) {
for (int i = 0; i < array.GetSize(); i++) {
if ((entity_t*)array.GetAt(i) == ent) {
return true;
}
}
return false;
}
static void Outliner_ClearHiddenEntities() {
s_outlinerHiddenEntities.RemoveAll();
}
static void Outliner_SyncVisibilityMapIdentity() {
s_outlinerVisibilityInitialized = true;
s_outlinerVisibilityWorldEntity = world_entity;
s_outlinerVisibilityFirstEntity = Outliner_FirstMapEntity();
s_outlinerVisibilityEntityCount = g_qeglobals.d_num_entities;
s_outlinerVisibilityMapModified = mapModified;
s_outlinerVisibilityMapName = Outliner_CurrentMapName();
}
void Outliner_ResetEntityVisibility() {
Outliner_ClearHiddenEntities();
Outliner_SyncVisibilityMapIdentity();
}
static void Outliner_EnsureVisibilityMap() {
const char* mapName = Outliner_CurrentMapName();
const entity_t* firstEntity = Outliner_FirstMapEntity();
bool resetVisibility = false;
if (!s_outlinerVisibilityInitialized) {
resetVisibility = true;
}
else if (s_outlinerVisibilityWorldEntity != world_entity) {
resetVisibility = true;
}
else if (s_outlinerVisibilityMapName.CompareNoCase(mapName) != 0) {
resetVisibility = true;
}
else if (mapModified < s_outlinerVisibilityMapModified) {
// Loading or creating a map normally resets mapModified. Treat that as
// a new editor visibility session so every entity starts checked.
resetVisibility = true;
}
else if (mapModified <= 0 && s_outlinerVisibilityFirstEntity != firstEntity) {
// Some map/new-map paths reuse the same worldspawn pointer and leave
// currentmap empty. If the list head changed while the map is clean,
// this is almost certainly a freshly loaded/new map, not an edit.
resetVisibility = true;
}
if (resetVisibility) {
Outliner_ClearHiddenEntities();
}
Outliner_SyncVisibilityMapIdentity();
}
static int Outliner_FindHiddenEntity(entity_t* ent) {
for (int i = 0; i < s_outlinerHiddenEntities.GetSize(); i++) {
if ((entity_t*)s_outlinerHiddenEntities.GetAt(i) == ent) {
return i;
}
}
return -1;
}
static void Outliner_PruneHiddenEntities(const CPtrArray& currentEntities) {
Outliner_EnsureVisibilityMap();
for (int i = s_outlinerHiddenEntities.GetSize() - 1; i >= 0; i--) {
entity_t* ent = (entity_t*)s_outlinerHiddenEntities.GetAt(i);
if (!Outliner_ArrayContainsPtr(currentEntities, ent)) {
s_outlinerHiddenEntities.RemoveAt(i);
}
}
}
bool Outliner_IsEntityVisible(entity_t* ent) {
if (ent == NULL) {
return true;
}
Outliner_EnsureVisibilityMap();
return Outliner_FindHiddenEntity(ent) < 0;
}
bool Outliner_HasHiddenEntities() {
Outliner_EnsureVisibilityMap();
return s_outlinerHiddenEntities.GetSize() > 0;
}
bool Outliner_IsEntityLinkVisible(entity_t* source, entity_t* target) {
return Outliner_IsEntityVisible(source) && Outliner_IsEntityVisible(target);
}
void Outliner_SetEntityVisible(entity_t* ent, bool visible) {
if (ent == NULL) {
return;
}
Outliner_EnsureVisibilityMap();
const int index = Outliner_FindHiddenEntity(ent);
if (visible) {
if (index >= 0) {
s_outlinerHiddenEntities.RemoveAt(index);
}
}
else {
if (index < 0) {
s_outlinerHiddenEntities.Add(ent);
}
}
}
BEGIN_MESSAGE_MAP(COutlinerDlg, CWnd)
ON_WM_CREATE()
ON_WM_SIZE()
@@ -53,6 +191,8 @@ BEGIN_MESSAGE_MAP(COutlinerDlg, CWnd)
ON_EN_CHANGE(COutlinerDlg::IDC_OUTLINER_FILTER, OnFilterChanged)
ON_NOTIFY(NM_DBLCLK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeDblClick)
ON_NOTIFY(NM_RCLICK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeRightClick)
ON_NOTIFY(NM_CLICK, COutlinerDlg::IDC_OUTLINER_TREE, OnTreeClick)
ON_MESSAGE(COutlinerDlg::WM_OUTLINER_APPLY_CHECK, OnApplyTreeCheck)
END_MESSAGE_MAP()
COutlinerDlg::COutlinerDlg() {
@@ -133,7 +273,7 @@ int COutlinerDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) {
m_tree.Create(
WS_CHILD | WS_VISIBLE | WS_TABSTOP |
TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT |
TVS_SHOWSELALWAYS | TVS_DISABLEDRAGDROP,
TVS_SHOWSELALWAYS | TVS_DISABLEDRAGDROP | TVS_CHECKBOXES,
CRect(0, 0, 10, 10),
this,
IDC_OUTLINER_TREE
@@ -299,6 +439,15 @@ BOOL COutlinerDlg::PreTranslateMessage(MSG* pMsg) {
OpenEntityProperties(SelectedEntity());
return TRUE;
}
if (pMsg->wParam == VK_SPACE && pMsg->hwnd == m_tree.GetSafeHwnd()) {
entity_t* ent = SelectedEntity();
if (ent != NULL) {
ToggleEntityVisibility(ent, false);
return TRUE;
}
}
if (pMsg->wParam == VK_F5) {
Refresh(true);
return TRUE;
@@ -349,7 +498,30 @@ void COutlinerDlg::Refresh(bool force) {
entity_t* oldSelection = SelectedEntity();
const char* mapName = Outliner_CurrentMapName();
const bool hadPreviousRefresh = (m_lastMapModified != -999999);
const bool mapIdentityChanged =
(!s_outlinerVisibilityInitialized) ||
(m_lastWorldEntity != world_entity) ||
(m_lastMapName.CompareNoCase(mapName) != 0) ||
(hadPreviousRefresh && mapModified < m_lastMapModified) ||
(hadPreviousRefresh && mapModified <= 0 && s_outlinerVisibilityFirstEntity != Outliner_FirstMapEntity());
// File->New in this editor can leave the same empty map name around.
// When that happens, make the new map start from a clean all-visible state
// instead of inheriting hidden pointers from the previous contents.
const bool likelyNewBlankMap =
(s_outlinerHiddenEntities.GetSize() > 0) &&
(m_lastEntityCount > 1) &&
(g_qeglobals.d_num_entities <= 1) &&
(mapName[0] == '\0');
if (mapIdentityChanged || likelyNewBlankMap) {
Outliner_ResetEntityVisibility();
}
BuildGraph();
Outliner_PruneHiddenEntities(m_entities);
m_tree.SetRedraw(FALSE);
m_tree.DeleteAllItems();
@@ -383,6 +555,7 @@ void COutlinerDlg::Refresh(bool force) {
if (loopGroup == NULL) {
loopGroup = m_tree.InsertItem("Target loops / no root", TVI_ROOT, TVI_LAST);
m_tree.SetItemState(loopGroup, 0, TVIS_STATEIMAGEMASK);
insertedAny = true;
}
@@ -395,10 +568,14 @@ void COutlinerDlg::Refresh(bool force) {
}
if (!insertedAny) {
HTREEITEM emptyItem = NULL;
if (m_filterText.IsEmpty()) {
m_tree.InsertItem("<no entities>", TVI_ROOT, TVI_LAST);
emptyItem = m_tree.InsertItem("<no entities>", TVI_ROOT, TVI_LAST);
} else {
m_tree.InsertItem("<no matching entities>", TVI_ROOT, TVI_LAST);
emptyItem = m_tree.InsertItem("<no matching entities>", TVI_ROOT, TVI_LAST);
}
if (emptyItem != NULL) {
m_tree.SetItemState(emptyItem, 0, TVIS_STATEIMAGEMASK);
}
}
@@ -425,7 +602,7 @@ void COutlinerDlg::Refresh(bool force) {
m_lastWorldEntity = world_entity;
m_lastEntityCount = g_qeglobals.d_num_entities;
m_lastMapModified = mapModified;
m_lastMapName = currentmap;
m_lastMapName = Outliner_CurrentMapName();
}
void COutlinerDlg::BuildGraph() {
@@ -565,6 +742,10 @@ bool COutlinerDlg::IsTargetKey(const char* key) const {
return false;
}
if (idStr::Icmp(key, "next") == 0) {
return true;
}
return idStr::Icmpn(key, "target", 6) == 0;
}
@@ -723,6 +904,7 @@ HTREEITEM COutlinerDlg::InsertEntityRecursive(entity_t* ent, HTREEITEM parent, C
HTREEITEM item = m_tree.InsertItem(label, parent, TVI_LAST);
m_tree.SetItemData(item, (DWORD_PTR)AllocItemData(ent, cycleRef));
m_tree.SetCheck(item, Outliner_IsEntityVisible(ent) ? TRUE : FALSE);
const int nodeIndex = FindNode(ent);
if (nodeIndex >= 0) {
@@ -787,12 +969,170 @@ void COutlinerDlg::ExpandAll(HTREEITEM start) {
}
}
void COutlinerDlg::OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult) {
CPoint point;
::GetCursorPos(&point);
m_tree.ScreenToClient(&point);
UINT flags = 0;
HTREEITEM item = m_tree.HitTest(point, &flags);
if (item != NULL && (flags & TVHT_ONITEMSTATEICON) && EntityFromItem(item) != NULL) {
m_tree.SelectItem(item);
// Let the tree control finish changing the checkbox before we read it.
PostMessage(WM_OUTLINER_APPLY_CHECK, (WPARAM)item, 0);
}
*pResult = 0;
}
LRESULT COutlinerDlg::OnApplyTreeCheck(WPARAM wParam, LPARAM lParam) {
if (!m_tree.GetSafeHwnd()) {
return 0;
}
HTREEITEM item = (HTREEITEM)wParam;
if (item == NULL) {
return 0;
}
entity_t* ent = EntityFromItem(item);
if (ent == NULL) {
return 0;
}
const bool visible = m_tree.GetCheck(item) != FALSE;
Outliner_SetEntityVisible(ent, visible);
ApplyVisibilityChange();
return 0;
}
void COutlinerDlg::CollectEntityAndChildren(entity_t* ent, CPtrArray& out, CPtrArray& stack) const {
if (ent == NULL) {
return;
}
if (ArrayContains(out, ent)) {
return;
}
out.Add(ent);
if (ArrayContains(stack, ent)) {
return;
}
stack.Add(ent);
for (int i = 0; i < m_edges.GetSize(); i++) {
const outlinerEdge_t& edge = m_edges.GetAt(i);
if (edge.source != ent) {
continue;
}
CollectEntityAndChildren(edge.target, out, stack);
}
stack.RemoveAt(stack.GetSize() - 1);
}
void COutlinerDlg::SetEntityVisibility(entity_t* ent, bool visible, bool includeChildren) {
if (ent == NULL) {
return;
}
if (includeChildren) {
CPtrArray entitiesToChange;
CPtrArray stack;
CollectEntityAndChildren(ent, entitiesToChange, stack);
for (int i = 0; i < entitiesToChange.GetSize(); i++) {
Outliner_SetEntityVisible((entity_t*)entitiesToChange.GetAt(i), visible);
}
}
else {
Outliner_SetEntityVisible(ent, visible);
}
ApplyVisibilityChange();
}
void COutlinerDlg::ToggleEntityVisibility(entity_t* ent, bool includeChildren) {
if (ent == NULL) {
return;
}
const bool newVisible = !Outliner_IsEntityVisible(ent);
SetEntityVisibility(ent, newVisible, includeChildren);
}
void COutlinerDlg::ShowOnlyEntityAndChildren(entity_t* ent) {
if (ent == NULL) {
return;
}
CPtrArray keepVisible;
CPtrArray stack;
CollectEntityAndChildren(ent, keepVisible, stack);
for (int i = 0; i < m_entities.GetSize(); i++) {
entity_t* candidate = (entity_t*)m_entities.GetAt(i);
if (i == 0)
{
Outliner_SetEntityVisible(candidate, true);
continue;
}
Outliner_SetEntityVisible(candidate, ArrayContains(keepVisible, candidate));
}
ApplyVisibilityChange();
}
void COutlinerDlg::ShowAllEntities() {
Outliner_ResetEntityVisibility();
ApplyVisibilityChange();
}
void COutlinerDlg::UpdateAllCheckmarks(HTREEITEM start) {
HTREEITEM item = start;
while (item != NULL) {
entity_t* ent = EntityFromItem(item);
if (ent != NULL) {
m_tree.SetCheck(item, Outliner_IsEntityVisible(ent) ? TRUE : FALSE);
}
HTREEITEM child = m_tree.GetChildItem(item);
if (child != NULL) {
UpdateAllCheckmarks(child);
}
item = m_tree.GetNextSiblingItem(item);
}
}
void COutlinerDlg::ApplyVisibilityChange() {
if (m_tree.GetSafeHwnd()) {
m_tree.SetRedraw(FALSE);
UpdateAllCheckmarks(m_tree.GetRootItem());
m_tree.SetRedraw(TRUE);
m_tree.Invalidate();
}
Sys_UpdateWindows(W_ALL);
}
void COutlinerDlg::OnTreeDblClick(NMHDR* pNMHDR, LRESULT* pResult) {
OpenEntityProperties(SelectedEntity());
*pResult = 0;
}
void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) {
RefreshIfNeeded();
CPoint screenPoint;
::GetCursorPos(&screenPoint);
@@ -806,12 +1146,23 @@ void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) {
}
entity_t* ent = SelectedEntity();
const bool entVisible = Outliner_IsEntityVisible(ent);
CString toggleSelfText;
CString toggleChildrenText;
toggleSelfText.Format("%s this entity", entVisible ? "Hide" : "Show");
toggleChildrenText.Format("%s this entity + children", entVisible ? "Hide" : "Show");
CMenu menu;
menu.CreatePopupMenu();
menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_OPEN_PROPERTIES, "Open entity properties");
menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_MOVE_TO_ENTITY, "Move to entity");
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_TOGGLE_VISIBILITY, toggleSelfText);
menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN, toggleChildrenText);
menu.AppendMenu(MF_STRING | (ent ? 0 : MF_GRAYED), ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN, "Show only this entity + children");
menu.AppendMenu(MF_STRING, ID_OUTLINER_SHOW_ALL, "Show all entities");
menu.AppendMenu(MF_SEPARATOR);
menu.AppendMenu(MF_STRING, ID_OUTLINER_REFRESH, "Refresh");
const int command = menu.TrackPopupMenu(
@@ -828,6 +1179,18 @@ void COutlinerDlg::OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult) {
case ID_OUTLINER_MOVE_TO_ENTITY:
MoveToEntity(ent);
break;
case ID_OUTLINER_TOGGLE_VISIBILITY:
ToggleEntityVisibility(ent, false);
break;
case ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN:
ToggleEntityVisibility(ent, true);
break;
case ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN:
ShowOnlyEntityAndChildren(ent);
break;
case ID_OUTLINER_SHOW_ALL:
ShowAllEntities();
break;
case ID_OUTLINER_REFRESH:
Refresh(true);
break;
+22 -1
View File
@@ -43,7 +43,12 @@ public:
ID_OUTLINER_OPEN_PROPERTIES = 0x7310,
ID_OUTLINER_MOVE_TO_ENTITY = 0x7311,
ID_OUTLINER_REFRESH = 0x7312,
OUTLINER_REFRESH_TIMER = 0x7320
ID_OUTLINER_TOGGLE_VISIBILITY = 0x7313,
ID_OUTLINER_TOGGLE_VISIBILITY_CHILDREN = 0x7314,
ID_OUTLINER_SHOW_ONLY_THIS_AND_CHILDREN = 0x7315,
ID_OUTLINER_SHOW_ALL = 0x7316,
OUTLINER_REFRESH_TIMER = 0x7320,
WM_OUTLINER_APPLY_CHECK = WM_APP + 0x321
};
COutlinerDlg();
@@ -71,6 +76,8 @@ protected:
afx_msg void OnFilterChanged();
void OnTreeDblClick(NMHDR* pNMHDR, LRESULT* pResult);
void OnTreeRightClick(NMHDR* pNMHDR, LRESULT* pResult);
void OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg LRESULT OnApplyTreeCheck(WPARAM wParam, LPARAM lParam);
virtual BOOL PreTranslateMessage(MSG* pMsg);
@@ -128,7 +135,21 @@ private:
HTREEITEM FindTreeItemForEntity(entity_t* ent, HTREEITEM start) const;
void ExpandAll(HTREEITEM start);
void CollectEntityAndChildren(entity_t* ent, CPtrArray& out, CPtrArray& stack) const;
void SetEntityVisibility(entity_t* ent, bool visible, bool includeChildren);
void ToggleEntityVisibility(entity_t* ent, bool includeChildren);
void ShowOnlyEntityAndChildren(entity_t* ent);
void ShowAllEntities();
void UpdateAllCheckmarks(HTREEITEM start);
void ApplyVisibilityChange();
bool NeedsRefresh() const;
bool GetEntityCenter(entity_t* ent, idVec3& center) const;
void ApplyFont();
};
bool Outliner_IsEntityVisible(entity_t* ent);
void Outliner_SetEntityVisible(entity_t* ent, bool visible);
void Outliner_ResetEntityVisibility();
bool Outliner_HasHiddenEntities();
bool Outliner_IsEntityLinkVisible(entity_t* source, entity_t* target);